@habeetat/cli 0.1.0-dev.20260324164706.1f6ff3c → 0.1.0-dev.20260324202418.70eccf7

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/bin.js CHANGED
@@ -5,7 +5,7 @@ var commander = require('commander');
5
5
  var fs2 = require('fs');
6
6
  var path = require('path');
7
7
  var ora = require('ora');
8
- var chalk2 = require('chalk');
8
+ var chalk3 = require('chalk');
9
9
  var execa = require('execa');
10
10
  var readline = require('readline');
11
11
 
@@ -14,28 +14,28 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
14
  var fs2__default = /*#__PURE__*/_interopDefault(fs2);
15
15
  var path__default = /*#__PURE__*/_interopDefault(path);
16
16
  var ora__default = /*#__PURE__*/_interopDefault(ora);
17
- var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
17
+ var chalk3__default = /*#__PURE__*/_interopDefault(chalk3);
18
18
 
19
- var prefix = chalk2__default.default.cyan("[habeetat]");
19
+ var prefix = chalk3__default.default.cyan("[habeetat]");
20
20
  var logger = {
21
21
  info: (msg) => console.log(`${prefix} ${msg}`),
22
- success: (msg) => console.log(`${prefix} ${chalk2__default.default.green("\u2713")} ${msg}`),
23
- warn: (msg) => console.log(`${prefix} ${chalk2__default.default.yellow("\u26A0")} ${msg}`),
24
- error: (msg) => console.error(`${prefix} ${chalk2__default.default.red("\u2717")} ${msg}`),
22
+ success: (msg) => console.log(`${prefix} ${chalk3__default.default.green("\u2713")} ${msg}`),
23
+ warn: (msg) => console.log(`${prefix} ${chalk3__default.default.yellow("\u26A0")} ${msg}`),
24
+ error: (msg) => console.error(`${prefix} ${chalk3__default.default.red("\u2717")} ${msg}`),
25
25
  phase: (msg) => {
26
26
  console.log("");
27
- console.log(chalk2__default.default.cyan("\u2550".repeat(56)));
28
- console.log(chalk2__default.default.cyan(` ${msg}`));
29
- console.log(chalk2__default.default.cyan("\u2550".repeat(56)));
27
+ console.log(chalk3__default.default.cyan("\u2550".repeat(56)));
28
+ console.log(chalk3__default.default.cyan(` ${msg}`));
29
+ console.log(chalk3__default.default.cyan("\u2550".repeat(56)));
30
30
  console.log("");
31
31
  },
32
32
  banner: () => {
33
33
  console.log("");
34
- console.log(chalk2__default.default.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
35
- console.log(chalk2__default.default.cyan("\u2551 \u2551"));
36
- console.log(chalk2__default.default.cyan("\u2551 Habeetat Platform CLI \u2551"));
37
- console.log(chalk2__default.default.cyan("\u2551 \u2551"));
38
- console.log(chalk2__default.default.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
34
+ console.log(chalk3__default.default.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
35
+ console.log(chalk3__default.default.cyan("\u2551 \u2551"));
36
+ console.log(chalk3__default.default.cyan("\u2551 Habeetat Platform CLI \u2551"));
37
+ console.log(chalk3__default.default.cyan("\u2551 \u2551"));
38
+ console.log(chalk3__default.default.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
39
39
  console.log("");
40
40
  }
41
41
  };
@@ -746,13 +746,13 @@ async function doctorCommand() {
746
746
  logger.phase("Habeetat Doctor \u2014 Diagnostics");
747
747
  let issues = 0;
748
748
  const dockerOk = await checkDocker();
749
- console.log(` Docker: ${dockerOk ? chalk2__default.default.green("\u2713 Installed") : chalk2__default.default.red("\u2717 Not found")}`);
749
+ console.log(` Docker: ${dockerOk ? chalk3__default.default.green("\u2713 Installed") : chalk3__default.default.red("\u2717 Not found")}`);
750
750
  if (!dockerOk) issues++;
751
751
  const composeOk = await checkDockerCompose();
752
- console.log(` Docker Compose: ${composeOk ? chalk2__default.default.green("\u2713 Available") : chalk2__default.default.red("\u2717 Not found")}`);
752
+ console.log(` Docker Compose: ${composeOk ? chalk3__default.default.green("\u2713 Available") : chalk3__default.default.red("\u2717 Not found")}`);
753
753
  if (!composeOk) issues++;
754
754
  const projectDir = findProjectRoot();
755
- console.log(` Project dir: ${projectDir ? chalk2__default.default.green(`\u2713 ${projectDir}`) : chalk2__default.default.yellow("\u26A0 Not in a Habeetat project")}`);
755
+ console.log(` Project dir: ${projectDir ? chalk3__default.default.green(`\u2713 ${projectDir}`) : chalk3__default.default.yellow("\u26A0 Not in a Habeetat project")}`);
756
756
  if (!projectDir) {
757
757
  issues++;
758
758
  console.log("");
@@ -760,25 +760,25 @@ async function doctorCommand() {
760
760
  return;
761
761
  }
762
762
  const configExists = fs2__default.default.existsSync(path__default.default.join(projectDir, CONFIG_FILE));
763
- console.log(` ${CONFIG_FILE}: ${configExists ? chalk2__default.default.green("\u2713 Found") : chalk2__default.default.red("\u2717 Missing")}`);
763
+ console.log(` ${CONFIG_FILE}: ${configExists ? chalk3__default.default.green("\u2713 Found") : chalk3__default.default.red("\u2717 Missing")}`);
764
764
  if (!configExists) issues++;
765
765
  const composeExists = fs2__default.default.existsSync(path__default.default.join(projectDir, COMPOSE_FILE));
766
- console.log(` ${COMPOSE_FILE}: ${composeExists ? chalk2__default.default.green("\u2713 Found") : chalk2__default.default.red("\u2717 Missing")}`);
766
+ console.log(` ${COMPOSE_FILE}: ${composeExists ? chalk3__default.default.green("\u2713 Found") : chalk3__default.default.red("\u2717 Missing")}`);
767
767
  if (!composeExists) issues++;
768
768
  const envExists = fs2__default.default.existsSync(path__default.default.join(projectDir, ENV_FILE));
769
- console.log(` ${ENV_FILE}: ${envExists ? chalk2__default.default.green("\u2713 Found") : chalk2__default.default.red("\u2717 Missing")}`);
769
+ console.log(` ${ENV_FILE}: ${envExists ? chalk3__default.default.green("\u2713 Found") : chalk3__default.default.red("\u2717 Missing")}`);
770
770
  if (!envExists) issues++;
771
771
  const nginxExists = fs2__default.default.existsSync(path__default.default.join(projectDir, "nginx", "platform.conf"));
772
- console.log(` nginx/platform.conf: ${nginxExists ? chalk2__default.default.green("\u2713 Found") : chalk2__default.default.red("\u2717 Missing")}`);
772
+ console.log(` nginx/platform.conf: ${nginxExists ? chalk3__default.default.green("\u2713 Found") : chalk3__default.default.red("\u2717 Missing")}`);
773
773
  if (!nginxExists) issues++;
774
774
  if (configExists) {
775
775
  try {
776
776
  const config = loadConfig(projectDir);
777
- console.log(` Version: ${chalk2__default.default.cyan(config.version)}`);
778
- console.log(` Domain: ${chalk2__default.default.cyan(config.platform.domain)}`);
779
- console.log(` Protocol: ${chalk2__default.default.cyan(config.platform.protocol)}`);
777
+ console.log(` Version: ${chalk3__default.default.cyan(config.version)}`);
778
+ console.log(` Domain: ${chalk3__default.default.cyan(config.platform.domain)}`);
779
+ console.log(` Protocol: ${chalk3__default.default.cyan(config.platform.protocol)}`);
780
780
  } catch (err) {
781
- console.log(` Config: ${chalk2__default.default.red("\u2717 Invalid JSON")}`);
781
+ console.log(` Config: ${chalk3__default.default.red("\u2717 Invalid JSON")}`);
782
782
  issues++;
783
783
  }
784
784
  }
@@ -826,6 +826,200 @@ async function destroyCommand(options) {
826
826
  process.exit(1);
827
827
  }
828
828
  }
829
+ function loadEnv2(projectDir) {
830
+ const envPath = path__default.default.join(projectDir, ".env");
831
+ if (!fs2__default.default.existsSync(envPath)) return {};
832
+ const lines = fs2__default.default.readFileSync(envPath, "utf-8").split("\n");
833
+ const env = {};
834
+ for (const line of lines) {
835
+ const trimmed = line.trim();
836
+ if (!trimmed || trimmed.startsWith("#")) continue;
837
+ const eqIdx = trimmed.indexOf("=");
838
+ if (eqIdx === -1) continue;
839
+ env[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
840
+ }
841
+ return env;
842
+ }
843
+ async function runSeedScript(projectDir, command, args) {
844
+ const { stdout, stderr } = await execa.execa(
845
+ "docker",
846
+ [
847
+ "compose",
848
+ "-f",
849
+ COMPOSE_FILE,
850
+ "exec",
851
+ "-T",
852
+ "backend",
853
+ "npx",
854
+ "tsx",
855
+ "scripts/nhp-seed-dev.ts",
856
+ command,
857
+ ...args
858
+ ],
859
+ { cwd: projectDir, stdio: "pipe" }
860
+ );
861
+ if (stderr) process.stderr.write(stderr + "\n");
862
+ const jsonLine = stdout.trim().split("\n").pop() ?? "";
863
+ try {
864
+ return JSON.parse(jsonLine);
865
+ } catch {
866
+ throw new Error(`Script produced unexpected output: ${stdout}`);
867
+ }
868
+ }
869
+ function printSdkConfig(config, env, parts) {
870
+ const protocol = config.protocol || "https";
871
+ const domain = config.domain;
872
+ const iamUrl = env.LOGTO_EXTERNAL_ENDPOINT || `${protocol}://iam.${domain}`;
873
+ const apiUrl = env.IAM_AUDIENCE ? env.IAM_AUDIENCE.replace(/\/api$/, "") : `${protocol}://api.${domain}`;
874
+ console.log("");
875
+ console.log(chalk3__default.default.cyan("\u2550".repeat(56)));
876
+ console.log(chalk3__default.default.cyan(" SDK Configuration"));
877
+ console.log(chalk3__default.default.cyan("\u2550".repeat(56)));
878
+ console.log("");
879
+ if (parts.logtoAppId) {
880
+ console.log(chalk3__default.default.bold(" Frontend SDK (@logto/react + @habeetat/sdk-frontend)"));
881
+ console.log(chalk3__default.default.dim(" " + "\u2500".repeat(50)));
882
+ console.log(` VITE_LOGTO_ENDPOINT=${chalk3__default.default.green(iamUrl)}`);
883
+ console.log(` VITE_LOGTO_APP_ID=${chalk3__default.default.green(parts.logtoAppId)}`);
884
+ console.log(` VITE_LOGTO_API_RESOURCE=${chalk3__default.default.green(env.IAM_AUDIENCE || `${protocol}://api.${domain}/api`)}`);
885
+ if (parts.tenant) {
886
+ console.log(` VITE_APP_TENANT=${chalk3__default.default.green(parts.tenant)}`);
887
+ }
888
+ console.log("");
889
+ }
890
+ if (parts.tenant) {
891
+ console.log(chalk3__default.default.bold(" Backend SDK (@habeetat/sdk-backend)"));
892
+ console.log(chalk3__default.default.dim(" " + "\u2500".repeat(50)));
893
+ console.log(` HABEETAT_ENDPOINT=${chalk3__default.default.green(apiUrl)}`);
894
+ console.log(` HABEETAT_TENANT_SLUG=${chalk3__default.default.green(parts.tenant)}`);
895
+ console.log("");
896
+ }
897
+ if (parts.email && parts.password) {
898
+ console.log(chalk3__default.default.bold(" Test credentials"));
899
+ console.log(chalk3__default.default.dim(" " + "\u2500".repeat(50)));
900
+ console.log(` Email: ${chalk3__default.default.green(parts.email)}`);
901
+ console.log(` Password: ${chalk3__default.default.green(parts.password)}`);
902
+ console.log(` IAM: ${chalk3__default.default.green(iamUrl)}`);
903
+ console.log("");
904
+ }
905
+ console.log(chalk3__default.default.cyan("\u2550".repeat(56)));
906
+ console.log("");
907
+ }
908
+ async function seedTenantCommand(options) {
909
+ const projectDir = getProjectDir();
910
+ const config = loadConfig(projectDir);
911
+ const env = loadEnv2(projectDir);
912
+ const args = [
913
+ "--name",
914
+ options.name,
915
+ "--slug",
916
+ options.slug,
917
+ ...options.description ? ["--description", options.description] : []
918
+ ];
919
+ const spinner = ora__default.default(`Creating tenant "${options.slug}"...`).start();
920
+ let result;
921
+ try {
922
+ result = await runSeedScript(projectDir, "create-tenant", args);
923
+ spinner.succeed(`Tenant created: ${chalk3__default.default.bold(result.slug)} (${result.displayName})`);
924
+ } catch (err) {
925
+ spinner.fail("Failed to create tenant");
926
+ logger.error(String(err));
927
+ process.exit(1);
928
+ }
929
+ logger.info(` ID: ${result.id}`);
930
+ logger.info(` Logto Org ID: ${result.logtoOrganizationId}`);
931
+ printSdkConfig(config.platform, env, { tenant: result.slug });
932
+ }
933
+ async function seedAppCommand(options) {
934
+ const projectDir = getProjectDir();
935
+ const config = loadConfig(projectDir);
936
+ const env = loadEnv2(projectDir);
937
+ const args = [
938
+ "--name",
939
+ options.name,
940
+ "--slug",
941
+ options.slug,
942
+ ...options.description ? ["--description", options.description] : [],
943
+ ...options.tenant ? ["--tenant", options.tenant] : [],
944
+ ...options.redirectUri?.flatMap((u) => ["--redirect-uri", u]) ?? [],
945
+ ...options.logoutUri?.flatMap((u) => ["--logout-uri", u]) ?? [],
946
+ ...options.corsOrigin?.flatMap((o) => ["--cors-origin", o]) ?? []
947
+ ];
948
+ const spinner = ora__default.default(`Creating app "${options.slug}"...`).start();
949
+ let result;
950
+ try {
951
+ result = await runSeedScript(projectDir, "create-app", args);
952
+ spinner.succeed(`App created: ${chalk3__default.default.bold(result.slug)} (Logto app: ${result.logtoAppId})`);
953
+ } catch (err) {
954
+ spinner.fail("Failed to create app");
955
+ logger.error(String(err));
956
+ process.exit(1);
957
+ }
958
+ logger.info(` ID: ${result.id}`);
959
+ logger.info(` Logto App ID: ${result.logtoAppId}`);
960
+ if (result.redirectUris && result.redirectUris.length > 0) {
961
+ logger.info(` Redirect URIs: ${result.redirectUris.join(", ")}`);
962
+ }
963
+ if (result.tenantSlug) {
964
+ logger.info(` Installed on: ${result.tenantSlug}`);
965
+ }
966
+ printSdkConfig(config.platform, env, {
967
+ tenant: result.tenantSlug || options.tenant,
968
+ logtoAppId: result.logtoAppId
969
+ });
970
+ }
971
+ async function seedUserCommand(options) {
972
+ const projectDir = getProjectDir();
973
+ const config = loadConfig(projectDir);
974
+ const env = loadEnv2(projectDir);
975
+ const args = [
976
+ "--email",
977
+ options.email,
978
+ "--password",
979
+ options.password,
980
+ "--tenant",
981
+ options.tenant,
982
+ ...options.username ? ["--username", options.username] : [],
983
+ ...options.displayName ? ["--display-name", options.displayName] : [],
984
+ ...options.role ? ["--role", options.role] : []
985
+ ];
986
+ const spinner = ora__default.default(`Creating user "${options.email}" in tenant "${options.tenant}"...`).start();
987
+ let result;
988
+ try {
989
+ result = await runSeedScript(projectDir, "create-user", args);
990
+ spinner.succeed(`User created: ${chalk3__default.default.bold(result.email)} (Logto ID: ${result.logtoUserId})`);
991
+ } catch (err) {
992
+ spinner.fail("Failed to create user");
993
+ logger.error(String(err));
994
+ process.exit(1);
995
+ }
996
+ logger.info(` Logto User ID: ${result.logtoUserId}`);
997
+ logger.info(` Username: ${result.username}`);
998
+ logger.info(` Tenant: ${result.tenantSlug}`);
999
+ logger.info(` Role: ${result.role}${result.roleAssigned ? "" : " (not assigned \u2014 role not found in Logto)"}`);
1000
+ if (result.note) logger.info(` Note: ${result.note}`);
1001
+ printSdkConfig(config.platform, env, {
1002
+ tenant: result.tenantSlug,
1003
+ email: result.email,
1004
+ password: result.password
1005
+ });
1006
+ }
1007
+ function registerSeedCommand(program2) {
1008
+ const seed = program2.command("seed").description("Seed dev data \u2014 create tenants, apps and users for local development");
1009
+ seed.command("tenant").description("Create a new tenant (Logto org + platform DB)").requiredOption("--name <name>", 'Display name (e.g. "Acme Corp")').requiredOption("--slug <slug>", "Unique slug, lowercase alphanumeric + hyphens (e.g. acme)").option("--description <desc>", "Optional description").action((opts) => {
1010
+ return seedTenantCommand(opts);
1011
+ });
1012
+ seed.command("app").description("Create a new OIDC application (Logto SPA app + platform DB)").requiredOption("--name <name>", 'App display name (e.g. "My CRM")').requiredOption("--slug <slug>", "Unique slug, lowercase alphanumeric + hyphens (e.g. my-crm)").option("--redirect-uri <uri...>", "OIDC redirect URI(s) (e.g. http://localhost:3000/callback)").option("--logout-uri <uri...>", "Post-logout redirect URI(s)").option("--cors-origin <origin...>", "CORS allowed origin(s)").option("--description <desc>", "Optional description").option("--tenant <slug>", "Install app on this tenant after creation").action(
1013
+ (opts) => {
1014
+ return seedAppCommand(opts);
1015
+ }
1016
+ );
1017
+ seed.command("user").description("Create a new user in Logto and add to a tenant organization").requiredOption("--email <email>", "User email address").requiredOption("--password <password>", "User password").requiredOption("--tenant <slug>", "Tenant slug to add the user to").option("--username <username>", "Logto username (defaults to email prefix)").option("--display-name <name>", "Display name (defaults to username)").option("--role <role>", "Organization role to assign (default: TENANT_ADMIN)", "TENANT_ADMIN").action(
1018
+ (opts) => {
1019
+ return seedUserCommand(opts);
1020
+ }
1021
+ );
1022
+ }
829
1023
 
830
1024
  // src/bin.ts
831
1025
  var pkg = JSON.parse(
@@ -847,6 +1041,7 @@ program.command("destroy").description("Destroy the platform \u2014 remove all c
847
1041
  program.command("init").description("Initialize platform (seed Logto apps, create admin user, setup database)").action(() => {
848
1042
  return initPlatform(getProjectDir());
849
1043
  });
1044
+ registerSeedCommand(program);
850
1045
  program.parse();
851
1046
  //# sourceMappingURL=bin.js.map
852
1047
  //# sourceMappingURL=bin.js.map