alepha 0.14.0 → 0.14.2

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.
Files changed (149) hide show
  1. package/README.md +3 -3
  2. package/dist/api/audits/index.d.ts +80 -1
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js.map +1 -1
  5. package/dist/api/files/index.d.ts +80 -1
  6. package/dist/api/files/index.d.ts.map +1 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +236 -157
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/jobs/index.js.map +1 -1
  11. package/dist/api/notifications/index.d.ts +21 -1
  12. package/dist/api/notifications/index.d.ts.map +1 -1
  13. package/dist/api/parameters/index.d.ts +451 -4
  14. package/dist/api/parameters/index.d.ts.map +1 -1
  15. package/dist/api/parameters/index.js.map +1 -1
  16. package/dist/api/users/index.d.ts +252 -249
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +4 -0
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +128 -128
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.js.map +1 -1
  23. package/dist/cache/core/index.js.map +1 -1
  24. package/dist/cli/index.d.ts +304 -115
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/index.js +650 -531
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/command/index.d.ts +210 -13
  29. package/dist/command/index.d.ts.map +1 -1
  30. package/dist/command/index.js +306 -69
  31. package/dist/command/index.js.map +1 -1
  32. package/dist/core/index.browser.js.map +1 -1
  33. package/dist/core/index.d.ts +1 -1
  34. package/dist/core/index.d.ts.map +1 -1
  35. package/dist/core/index.js +7 -6
  36. package/dist/core/index.js.map +1 -1
  37. package/dist/core/index.native.js +7 -6
  38. package/dist/core/index.native.js.map +1 -1
  39. package/dist/datetime/index.js.map +1 -1
  40. package/dist/fake/index.js.map +1 -1
  41. package/dist/file/index.d.ts.map +1 -1
  42. package/dist/file/index.js.map +1 -1
  43. package/dist/lock/redis/index.js.map +1 -1
  44. package/dist/logger/index.js.map +1 -1
  45. package/dist/mcp/index.js.map +1 -1
  46. package/dist/orm/index.browser.js +26 -5
  47. package/dist/orm/index.browser.js.map +1 -1
  48. package/dist/orm/index.d.ts +294 -215
  49. package/dist/orm/index.d.ts.map +1 -1
  50. package/dist/orm/index.js +522 -523
  51. package/dist/orm/index.js.map +1 -1
  52. package/dist/queue/redis/index.js +2 -4
  53. package/dist/queue/redis/index.js.map +1 -1
  54. package/dist/redis/index.d.ts +400 -29
  55. package/dist/redis/index.d.ts.map +1 -1
  56. package/dist/redis/index.js +412 -21
  57. package/dist/redis/index.js.map +1 -1
  58. package/dist/retry/index.js.map +1 -1
  59. package/dist/router/index.js.map +1 -1
  60. package/dist/scheduler/index.js.map +1 -1
  61. package/dist/security/index.d.ts.map +1 -1
  62. package/dist/security/index.js.map +1 -1
  63. package/dist/server/auth/index.d.ts +155 -155
  64. package/dist/server/auth/index.js.map +1 -1
  65. package/dist/server/cache/index.js.map +1 -1
  66. package/dist/server/cookies/index.browser.js.map +1 -1
  67. package/dist/server/cookies/index.js.map +1 -1
  68. package/dist/server/core/index.browser.js.map +1 -1
  69. package/dist/server/core/index.d.ts +0 -1
  70. package/dist/server/core/index.d.ts.map +1 -1
  71. package/dist/server/core/index.js.map +1 -1
  72. package/dist/server/helmet/index.d.ts +4 -1
  73. package/dist/server/helmet/index.d.ts.map +1 -1
  74. package/dist/server/helmet/index.js.map +1 -1
  75. package/dist/server/links/index.browser.js.map +1 -1
  76. package/dist/server/links/index.js.map +1 -1
  77. package/dist/server/multipart/index.d.ts.map +1 -1
  78. package/dist/server/multipart/index.js.map +1 -1
  79. package/dist/server/proxy/index.js.map +1 -1
  80. package/dist/server/rate-limit/index.js.map +1 -1
  81. package/dist/server/security/index.d.ts +9 -9
  82. package/dist/server/security/index.js.map +1 -1
  83. package/dist/server/swagger/index.js.map +1 -1
  84. package/dist/thread/index.js.map +1 -1
  85. package/dist/topic/core/index.js.map +1 -1
  86. package/dist/topic/redis/index.js +3 -3
  87. package/dist/topic/redis/index.js.map +1 -1
  88. package/dist/vite/index.js +9 -6
  89. package/dist/vite/index.js.map +1 -1
  90. package/dist/websocket/index.browser.js.map +1 -1
  91. package/dist/websocket/index.d.ts +7 -7
  92. package/dist/websocket/index.js.map +1 -1
  93. package/package.json +3 -3
  94. package/src/api/users/index.ts +4 -0
  95. package/src/cli/apps/AlephaCli.ts +36 -14
  96. package/src/cli/apps/AlephaPackageBuilderCli.ts +5 -1
  97. package/src/cli/assets/appRouterTs.ts +1 -1
  98. package/src/cli/atoms/changelogOptions.ts +45 -0
  99. package/src/cli/commands/{ViteCommands.ts → build.ts} +4 -93
  100. package/src/cli/commands/changelog.ts +244 -0
  101. package/src/cli/commands/clean.ts +14 -0
  102. package/src/cli/commands/{DrizzleCommands.ts → db.ts} +37 -124
  103. package/src/cli/commands/deploy.ts +118 -0
  104. package/src/cli/commands/dev.ts +57 -0
  105. package/src/cli/commands/format.ts +17 -0
  106. package/src/cli/commands/{CoreCommands.ts → init.ts} +2 -40
  107. package/src/cli/commands/lint.ts +17 -0
  108. package/src/cli/commands/root.ts +32 -0
  109. package/src/cli/commands/run.ts +24 -0
  110. package/src/cli/commands/test.ts +42 -0
  111. package/src/cli/commands/typecheck.ts +19 -0
  112. package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
  113. package/src/cli/defineConfig.ts +24 -0
  114. package/src/cli/index.ts +17 -5
  115. package/src/cli/services/AlephaCliUtils.ts +4 -21
  116. package/src/cli/services/GitMessageParser.ts +77 -0
  117. package/src/command/helpers/EnvUtils.ts +37 -0
  118. package/src/command/index.ts +3 -1
  119. package/src/command/primitives/$command.ts +172 -6
  120. package/src/command/providers/CliProvider.ts +424 -91
  121. package/src/core/Alepha.ts +8 -5
  122. package/src/file/providers/NodeFileSystemProvider.ts +3 -1
  123. package/src/orm/index.browser.ts +1 -1
  124. package/src/orm/index.ts +18 -10
  125. package/src/orm/interfaces/PgQueryWhere.ts +1 -26
  126. package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
  127. package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
  128. package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
  129. package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
  130. package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
  131. package/src/orm/services/QueryManager.ts +10 -125
  132. package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
  133. package/src/redis/index.ts +65 -3
  134. package/src/redis/providers/BunRedisProvider.ts +304 -0
  135. package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
  136. package/src/redis/providers/NodeRedisProvider.ts +280 -0
  137. package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
  138. package/src/redis/providers/RedisProvider.ts +134 -140
  139. package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
  140. package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
  141. package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
  142. package/src/server/core/providers/ServerProvider.ts +7 -4
  143. package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
  144. package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
  145. package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
  146. package/src/vite/tasks/buildServer.ts +1 -0
  147. package/src/cli/commands/BiomeCommands.ts +0 -29
  148. package/src/cli/commands/ChangelogCommands.ts +0 -389
  149. package/src/orm/services/PgJsonQueryManager.ts +0 -511
package/dist/cli/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { join } from "node:path";
3
- import { $env, $hook, $inject, $module, Alepha, AlephaError, OPTIONS, t } from "alepha";
3
+ import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, OPTIONS, t } from "alepha";
4
4
  import { FileSystemProvider } from "alepha/file";
5
- import { $command, CliProvider } from "alepha/command";
6
- import { $logger } from "alepha/logger";
7
- import { exec, spawn } from "node:child_process";
8
5
  import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
6
+ import { $command, CliProvider, EnvUtils } from "alepha/command";
7
+ import { $logger } from "alepha/logger";
9
8
  import { boot, buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
9
+ import { exec, spawn } from "node:child_process";
10
10
  import { tsImport } from "tsx/esm/api";
11
11
  import { readFileSync } from "node:fs";
12
12
  import { promisify } from "node:util";
@@ -14,7 +14,7 @@ import * as os from "node:os";
14
14
 
15
15
  //#region ../../src/cli/assets/appRouterTs.ts
16
16
  const appRouterTs = () => `
17
- import { $page } from "@alepha/react";
17
+ import { $page } from "@alepha/react/router";
18
18
 
19
19
  export class AppRouter {
20
20
  home = $page({
@@ -173,6 +173,7 @@ const version = packageJson.version;
173
173
  var AlephaCliUtils = class {
174
174
  log = $logger();
175
175
  fs = $inject(FileSystemProvider);
176
+ envUtils = $inject(EnvUtils);
176
177
  /**
177
178
  * Execute a command using npx with inherited stdio.
178
179
  *
@@ -461,23 +462,8 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
461
462
  * Reads the .env file in the specified root directory and sets
462
463
  * the environment variables in process.env.
463
464
  */
464
- async loadEnvFile(root, files = [".env"]) {
465
- for (const it of files) for (const file of [it, `${it}.local`]) {
466
- const envPath = join(root, file);
467
- try {
468
- const lines = (await readFile(envPath, "utf8")).split("\n");
469
- for (const line of lines) {
470
- const [key, ...rest] = line.split("=");
471
- if (key) {
472
- const value = rest.join("=");
473
- process.env[key.trim()] = value.trim();
474
- }
475
- }
476
- this.log.debug(`Loaded environment variables from ${envPath}`);
477
- } catch {
478
- this.log.debug(`No ${file} file found at ${envPath}, skipping load.`);
479
- }
480
- }
465
+ async loadEnv(root, files = [".env"]) {
466
+ await this.envUtils.loadEnv(root, files);
481
467
  }
482
468
  async getPackageManager(root, flags) {
483
469
  if (flags?.yarn) return "yarn";
@@ -601,33 +587,144 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
601
587
  };
602
588
 
603
589
  //#endregion
604
- //#region ../../src/cli/commands/BiomeCommands.ts
605
- var BiomeCommands = class {
590
+ //#region ../../src/cli/commands/build.ts
591
+ var BuildCommand = class {
606
592
  log = $logger();
607
593
  utils = $inject(AlephaCliUtils);
608
- format = $command({
609
- name: "format",
610
- description: "Format the codebase using Biome",
611
- handler: async ({ root }) => {
612
- await this.utils.ensureConfig(root, { biomeJson: true });
613
- await this.utils.ensureDependency(root, "@biomejs/biome");
614
- await this.utils.exec(`biome format --fix`);
615
- }
616
- });
617
- lint = $command({
618
- name: "lint",
619
- description: "Run linter across the codebase using Biome",
620
- handler: async ({ root }) => {
621
- await this.utils.ensureConfig(root, { biomeJson: true });
622
- await this.utils.ensureDependency(root, "@biomejs/biome");
623
- await this.utils.exec(`biome check --formatter-enabled=false --fix`);
594
+ build = $command({
595
+ name: "build",
596
+ description: "Build the project for production",
597
+ args: t.optional(t.text({
598
+ title: "path",
599
+ description: "Filepath to build"
600
+ })),
601
+ flags: t.object({
602
+ stats: t.optional(t.boolean({ description: "Generate build stats report" })),
603
+ vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
604
+ cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
605
+ docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
606
+ sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
607
+ }),
608
+ handler: async ({ flags, args, run, root }) => {
609
+ process.env.ALEPHA_BUILD_MODE = "cli";
610
+ process.env.NODE_ENV = "production";
611
+ if (await this.utils.hasExpo(root)) return;
612
+ await this.utils.ensureConfig(root, {
613
+ viteConfigTs: true,
614
+ tsconfigJson: true
615
+ });
616
+ const entry = await boot.getServerEntry(root, args);
617
+ this.log.trace("Entry file found", { entry });
618
+ const distDir = "dist";
619
+ const clientDir = "public";
620
+ await this.utils.ensureDependency(root, "vite", { run });
621
+ await run.rm("dist", { alias: "clean dist" });
622
+ const viteAlephaBuildOptions = (await createRequire(import.meta.url)("vite").resolveConfig({}, "build", "production")).plugins.find((it) => it.name === "alepha:build")?.[OPTIONS] || {};
623
+ await this.utils.loadEnv(root, [".env", ".env.production"]);
624
+ const stats = flags.stats ?? viteAlephaBuildOptions.stats ?? false;
625
+ const hasServer = viteAlephaBuildOptions.serverEntry !== false;
626
+ let hasClient = false;
627
+ try {
628
+ await access(join(root, "index.html"));
629
+ hasClient = true;
630
+ } catch {}
631
+ const clientOptions = typeof viteAlephaBuildOptions.client === "object" ? viteAlephaBuildOptions.client : {};
632
+ if (hasClient) await run({
633
+ name: "vite build client",
634
+ handler: () => buildClient({
635
+ silent: true,
636
+ dist: `${distDir}/${clientDir}`,
637
+ stats,
638
+ precompress: clientOptions.precompress
639
+ })
640
+ });
641
+ await run({
642
+ name: "vite build server",
643
+ handler: async () => {
644
+ let clientBuilt = false;
645
+ try {
646
+ await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
647
+ clientBuilt = true;
648
+ } catch {}
649
+ await buildServer({
650
+ silent: true,
651
+ entry,
652
+ distDir,
653
+ clientDir: clientBuilt ? clientDir : void 0,
654
+ stats
655
+ });
656
+ if (clientBuilt && hasServer) await unlink(`${distDir}/${clientDir}/index.html`);
657
+ }
658
+ });
659
+ await copyAssets({
660
+ root,
661
+ entry: `${distDir}/index.js`,
662
+ distDir,
663
+ run
664
+ });
665
+ if (hasClient) {
666
+ const sitemapBaseUrl = flags.sitemap ?? clientOptions.sitemap?.hostname;
667
+ if (sitemapBaseUrl) await run({
668
+ name: "add sitemap",
669
+ handler: async () => {
670
+ await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
671
+ entry: `${distDir}/index.js`,
672
+ baseUrl: sitemapBaseUrl
673
+ }));
674
+ }
675
+ });
676
+ if (clientOptions.prerender) await run({
677
+ name: "pre-render pages",
678
+ handler: async () => {
679
+ await prerenderPages({
680
+ dist: `${distDir}/${clientDir}`,
681
+ entry: `${distDir}/index.js`,
682
+ compress: clientOptions.precompress
683
+ });
684
+ }
685
+ });
686
+ }
687
+ if (flags.vercel || viteAlephaBuildOptions.vercel) {
688
+ const config = typeof viteAlephaBuildOptions.vercel === "object" ? viteAlephaBuildOptions.vercel : {};
689
+ await run({
690
+ name: "add Vercel config",
691
+ handler: () => generateVercel({
692
+ distDir,
693
+ clientDir,
694
+ config
695
+ })
696
+ });
697
+ }
698
+ if (flags.cloudflare || viteAlephaBuildOptions.cloudflare) {
699
+ const config = typeof viteAlephaBuildOptions.cloudflare === "boolean" ? {} : viteAlephaBuildOptions.cloudflare;
700
+ await run({
701
+ name: "add Cloudflare config",
702
+ handler: () => generateCloudflare({
703
+ distDir,
704
+ config
705
+ })
706
+ });
707
+ }
708
+ if (flags.docker || viteAlephaBuildOptions.docker) {
709
+ const dockerConfig = typeof viteAlephaBuildOptions.docker === "object" ? viteAlephaBuildOptions.docker : {};
710
+ await run({
711
+ name: "add Docker config",
712
+ handler: () => generateDocker({
713
+ distDir,
714
+ ...dockerConfig
715
+ })
716
+ });
717
+ }
624
718
  }
625
719
  });
626
720
  };
627
721
 
628
722
  //#endregion
629
- //#region ../../src/cli/commands/ChangelogCommands.ts
630
- const execAsync = promisify(exec);
723
+ //#region ../../src/cli/atoms/changelogOptions.ts
724
+ /**
725
+ * Default scopes to ignore in changelog generation.
726
+ * Commits with these scopes won't appear in release notes.
727
+ */
631
728
  const DEFAULT_IGNORE = [
632
729
  "project",
633
730
  "release",
@@ -639,240 +736,208 @@ const DEFAULT_IGNORE = [
639
736
  "test",
640
737
  "style"
641
738
  ];
642
- function parseCommit(line, config) {
643
- const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
644
- if (!match) return null;
645
- const [, hash, message] = match;
646
- const breaking = message.includes("!:") || message.toLowerCase().includes("breaking");
647
- const ignore = config.ignore ?? DEFAULT_IGNORE;
648
- const conventionalMatch = message.match(/^(feat|fix|docs|refactor|perf|revert)(?:\(([^)]+)\))?!?:\s*(.+)$/i);
649
- if (conventionalMatch) {
650
- const [, type, scope, description] = conventionalMatch;
651
- if (scope) {
652
- const baseScope = scope.split("/")[0];
653
- if (ignore.includes(baseScope) || ignore.includes(scope)) return null;
654
- }
655
- if (!scope && ignore.includes(type.toLowerCase())) return null;
739
+ /**
740
+ * Changelog configuration atom.
741
+ *
742
+ * Configure in `alepha.config.ts`:
743
+ * ```ts
744
+ * import { changelogOptions } from "alepha/cli";
745
+ *
746
+ * alepha.set(changelogOptions, {
747
+ * ignore: ["project", "release", "chore", "docs"],
748
+ * });
749
+ * ```
750
+ */
751
+ const changelogOptions = $atom({
752
+ name: "alepha.changelog",
753
+ schema: t.object({ ignore: t.optional(t.array(t.string())) }),
754
+ default: { ignore: DEFAULT_IGNORE }
755
+ });
756
+
757
+ //#endregion
758
+ //#region ../../src/cli/services/GitMessageParser.ts
759
+ /**
760
+ * Service for parsing git commit messages into structured format.
761
+ *
762
+ * Only parses **conventional commits with a scope**:
763
+ * - `feat(scope): description` → feature
764
+ * - `fix(scope): description` → bug fix
765
+ * - `feat(scope)!: description` → breaking change
766
+ *
767
+ * Commits without scope are ignored, allowing developers to commit
768
+ * work-in-progress changes without polluting release notes:
769
+ * - `cli: work in progress` → ignored (no type)
770
+ * - `fix: quick patch` → ignored (no scope)
771
+ * - `feat(cli): add command` → included
772
+ */
773
+ var GitMessageParser = class {
774
+ log = $logger();
775
+ /**
776
+ * Parse a git commit line into a structured Commit object.
777
+ *
778
+ * **Format:** `type(scope): description` or `type(scope)!: description`
779
+ *
780
+ * **Supported types:** feat, fix, docs, refactor, perf, revert
781
+ *
782
+ * **Breaking changes:** Use `!` before `:` (e.g., `feat(api)!: remove endpoint`)
783
+ *
784
+ * @returns Commit object or null if not matching/ignored
785
+ */
786
+ parseCommit(line, config) {
787
+ const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
788
+ if (!match) return null;
789
+ const [, hash, message] = match;
790
+ const ignore = config.ignore ?? DEFAULT_IGNORE;
791
+ const conventionalMatch = message.match(/^(feat|fix|docs|refactor|perf|revert)\(([^)]+)\)(!)?:\s*(.+)$/i);
792
+ if (!conventionalMatch) return null;
793
+ const [, type, scope, breakingMark, description] = conventionalMatch;
794
+ const baseScope = scope.split("/")[0];
795
+ if (ignore.includes(baseScope) || ignore.includes(scope)) return null;
796
+ const breaking = breakingMark === "!" || description.toLowerCase().includes("breaking");
656
797
  return {
657
798
  hash: hash.substring(0, 8),
658
799
  type: type.toLowerCase(),
659
- scope: scope || null,
800
+ scope,
660
801
  description: description.trim(),
661
802
  breaking
662
803
  };
663
804
  }
664
- const moduleMatch = message.match(/^([a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?):\s*(.+)$/i);
665
- if (moduleMatch) {
666
- const [, module, description] = moduleMatch;
667
- const baseModule = module.split("/")[0];
668
- if (ignore.includes(baseModule)) return null;
669
- if (config.scopes && !config.scopes.includes(baseModule)) return null;
670
- const desc = description.toLowerCase();
671
- let type = "improve";
672
- if (desc.includes("fix") || desc.includes("bug") || desc.includes("issue")) type = "fix";
673
- else if (desc.includes("add") || desc.includes("new") || desc.includes("implement")) type = "feat";
674
- return {
675
- hash: hash.substring(0, 8),
676
- type,
677
- scope: module,
678
- description: description.trim(),
679
- breaking
680
- };
805
+ };
806
+
807
+ //#endregion
808
+ //#region ../../src/cli/commands/changelog.ts
809
+ const execAsync = promisify(exec);
810
+ /**
811
+ * Git provider for executing git commands.
812
+ * Can be substituted in tests with a mock implementation.
813
+ */
814
+ var GitProvider = class {
815
+ async exec(cmd, cwd) {
816
+ const { stdout } = await execAsync(`git ${cmd}`, { cwd });
817
+ return stdout;
681
818
  }
682
- return null;
683
- }
684
- function formatCommit(commit) {
685
- return `- ${commit.scope ? `**${commit.scope}**: ` : ""}${commit.description} (\`${commit.hash}\`)`;
686
- }
687
- function generateChangelog(entries) {
688
- let output = "# Changelog\n\n";
689
- output += "All notable changes to this project will be documented in this file.\n\n";
690
- for (const entry of entries) {
691
- output += `## [${entry.version}] - ${entry.date}\n\n`;
692
- output += formatEntry(entry);
819
+ };
820
+ /**
821
+ * Changelog command for generating release notes from git commits.
822
+ *
823
+ * Usage:
824
+ * - `alepha changelog` - Show unreleased changes since latest tag to HEAD
825
+ * - `alepha changelog --from=1.0.0` - Show changes from version to HEAD
826
+ * - `alepha changelog --from=1.0.0 --to=1.1.0` - Show changes between two refs
827
+ * - `alepha changelog | tee -a CHANGELOG.md` - Append to file
828
+ */
829
+ var ChangelogCommand = class {
830
+ log = $logger();
831
+ git = $inject(GitProvider);
832
+ parser = $inject(GitMessageParser);
833
+ config = $use(changelogOptions);
834
+ /**
835
+ * Format a single commit line.
836
+ * Example: `- **cli**: add new command (\`abc1234\`)`
837
+ * Breaking changes are flagged: `- **cli**: add new command [BREAKING] (\`abc1234\`)`
838
+ */
839
+ formatCommit(commit) {
840
+ const breaking = commit.breaking ? " [BREAKING]" : "";
841
+ return `- **${commit.scope}**: ${commit.description}${breaking} (\`${commit.hash}\`)`;
693
842
  }
694
- return output;
695
- }
696
- function formatEntry(entry) {
697
- let output = "";
698
- if (entry.breaking.length > 0) {
699
- output += "### Breaking Changes\n\n";
700
- for (const commit of entry.breaking) output += `${formatCommit(commit)}\n`;
701
- output += "\n";
843
+ /**
844
+ * Format the changelog entry with sections.
845
+ */
846
+ formatEntry(entry) {
847
+ const sections = [];
848
+ if (entry.features.length > 0) {
849
+ sections.push("### Features\n");
850
+ for (const commit of entry.features) sections.push(this.formatCommit(commit));
851
+ sections.push("");
852
+ }
853
+ if (entry.fixes.length > 0) {
854
+ sections.push("### Bug Fixes\n");
855
+ for (const commit of entry.fixes) sections.push(this.formatCommit(commit));
856
+ sections.push("");
857
+ }
858
+ return sections.join("\n");
702
859
  }
703
- if (entry.features.length > 0) {
704
- output += "### Features\n\n";
705
- for (const commit of entry.features) output += `${formatCommit(commit)}\n`;
706
- output += "\n";
860
+ /**
861
+ * Parse git log output into a changelog entry.
862
+ */
863
+ parseCommits(commitsOutput) {
864
+ const entry = {
865
+ features: [],
866
+ fixes: []
867
+ };
868
+ for (const line of commitsOutput.trim().split("\n")) {
869
+ if (!line.trim()) continue;
870
+ const commit = this.parser.parseCommit(line, this.config);
871
+ if (!commit) {
872
+ this.log.trace("Skipping commit", { line });
873
+ continue;
874
+ }
875
+ this.log.trace("Parsed commit", { commit });
876
+ if (commit.type === "feat") entry.features.push(commit);
877
+ else if (commit.type === "fix") entry.fixes.push(commit);
878
+ }
879
+ return entry;
707
880
  }
708
- if (entry.fixes.length > 0) {
709
- output += "### Bug Fixes\n\n";
710
- for (const commit of entry.fixes) output += `${formatCommit(commit)}\n`;
711
- output += "\n";
881
+ /**
882
+ * Check if entry has any public commits.
883
+ */
884
+ hasChanges(entry) {
885
+ return entry.features.length > 0 || entry.fixes.length > 0;
712
886
  }
713
- return output;
714
- }
715
- async function loadConfig(root) {
716
- try {
717
- const pkgPath = join(root, "package.json");
718
- return JSON.parse(await readFile(pkgPath, "utf8")).changelog ?? {};
719
- } catch {
720
- return {};
887
+ /**
888
+ * Get the latest version tag.
889
+ */
890
+ async getLatestTag(git) {
891
+ return (await git("tag --sort=-version:refname")).trim().split("\n").filter((tag) => tag.match(/^\d+\.\d+\.\d+$/))[0] || null;
721
892
  }
722
- }
723
- var ChangelogCommands = class {
724
893
  changelog = $command({
725
894
  name: "changelog",
726
- description: "Generate CHANGELOG.md from git commits",
895
+ description: "Generate changelog from conventional commits (outputs to stdout)",
727
896
  flags: t.object({
728
- release: t.optional(t.boolean({
729
- when: ["--release", "-r"],
730
- description: "Output release notes for the latest version only (for GitHub Release)"
731
- })),
732
- preview: t.optional(t.boolean({
733
- when: ["--preview", "-p"],
734
- description: "Preview unreleased changes (commits since last tag)"
735
- })),
736
- output: t.optional(t.string({
737
- when: ["--output", "-o"],
738
- description: "Output file path (defaults to CHANGELOG.md, use - for stdout)"
897
+ from: t.optional(t.string({
898
+ aliases: ["f"],
899
+ description: "Starting ref (default: latest tag)"
739
900
  })),
740
- limit: t.optional(t.number({
741
- when: ["--limit", "-l"],
742
- description: "Limit number of versions to include"
901
+ to: t.optional(t.string({
902
+ aliases: ["t"],
903
+ description: "Ending ref (default: HEAD)"
743
904
  }))
744
905
  }),
745
- handler: async ({ flags, run, root }) => {
746
- const config = await loadConfig(root);
747
- const git = async (cmd) => {
748
- const { stdout } = await execAsync(`git ${cmd}`, { cwd: root });
749
- return stdout;
750
- };
751
- const tags = (await git("tag --sort=-version:refname")).trim().split("\n").filter((tag) => tag.match(/^\d+\.\d+\.\d+$/));
752
- if (tags.length === 0) throw new Error("No version tags found");
753
- if (flags.preview) {
754
- const latestTag = tags[0];
755
- const commitsOutput = await git(`log ${latestTag}..HEAD --oneline`);
756
- if (!commitsOutput.trim()) {
757
- console.log("No unreleased changes since", latestTag);
758
- return;
759
- }
760
- const entry = {
761
- version: "Unreleased",
762
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
763
- features: [],
764
- fixes: [],
765
- docs: [],
766
- improvements: [],
767
- breaking: []
768
- };
769
- for (const line of commitsOutput.trim().split("\n")) {
770
- if (!line.trim()) continue;
771
- const commit = parseCommit(line, config);
772
- if (!commit) continue;
773
- if (commit.breaking) entry.breaking.push(commit);
774
- switch (commit.type) {
775
- case "feat":
776
- entry.features.push(commit);
777
- break;
778
- case "fix":
779
- entry.fixes.push(commit);
780
- break;
781
- case "docs":
782
- entry.docs.push(commit);
783
- break;
784
- case "refactor":
785
- case "perf":
786
- case "improve":
787
- entry.improvements.push(commit);
788
- break;
789
- }
790
- }
791
- if (!(entry.features.length > 0 || entry.fixes.length > 0 || entry.breaking.length > 0)) {
792
- console.log("No public changes since", latestTag);
906
+ handler: async ({ flags, root }) => {
907
+ const git = (cmd) => this.git.exec(cmd, root);
908
+ let fromRef;
909
+ if (flags.from) {
910
+ fromRef = flags.from;
911
+ this.log.debug("Using specified from ref", { from: fromRef });
912
+ } else {
913
+ const latestTag = await this.getLatestTag(git);
914
+ if (!latestTag) {
915
+ process.stdout.write("No version tags found in repository\n");
793
916
  return;
794
917
  }
795
- console.log(`## [Unreleased] - since ${latestTag}\n`);
796
- console.log(formatEntry(entry));
797
- return;
918
+ fromRef = latestTag;
919
+ this.log.debug("Using latest tag", { from: fromRef });
798
920
  }
799
- const entries = [];
800
- const limit = flags.limit || (flags.release ? 1 : tags.length);
801
- for (let i = 0; i < Math.min(limit, tags.length); i++) {
802
- const tag = tags[i];
803
- const prevTag = tags[i + 1];
804
- const date = (await git(`log -1 --format=%ci ${tag}`)).trim().split(" ")[0];
805
- const commitsOutput = await git(`log ${prevTag ? `${prevTag}..${tag}` : tag} --oneline`);
806
- const entry = {
807
- version: tag,
808
- date,
809
- features: [],
810
- fixes: [],
811
- docs: [],
812
- improvements: [],
813
- breaking: []
814
- };
815
- for (const line of commitsOutput.trim().split("\n")) {
816
- if (!line.trim()) continue;
817
- const commit = parseCommit(line, config);
818
- if (!commit) continue;
819
- if (commit.breaking) entry.breaking.push(commit);
820
- switch (commit.type) {
821
- case "feat":
822
- entry.features.push(commit);
823
- break;
824
- case "fix":
825
- entry.fixes.push(commit);
826
- break;
827
- case "docs":
828
- entry.docs.push(commit);
829
- break;
830
- case "refactor":
831
- case "perf":
832
- case "improve":
833
- entry.improvements.push(commit);
834
- break;
835
- }
836
- }
837
- if (entry.features.length > 0 || entry.fixes.length > 0 || entry.breaking.length > 0) entries.push(entry);
921
+ const toRef = flags.to || "HEAD";
922
+ this.log.debug("Using to ref", { to: toRef });
923
+ const commitsOutput = await git(`log ${fromRef}..${toRef} --oneline`);
924
+ if (!commitsOutput.trim()) {
925
+ process.stdout.write(`No changes in range ${fromRef}..${toRef}\n`);
926
+ return;
838
927
  }
839
- if (entries.length === 0) {
840
- console.log("No public commits found");
928
+ const entry = this.parseCommits(commitsOutput);
929
+ if (!this.hasChanges(entry)) {
930
+ process.stdout.write(`No public changes in range ${fromRef}..${toRef}\n`);
841
931
  return;
842
932
  }
843
- let output;
844
- if (flags.release) output = formatEntry(entries[0]);
845
- else output = generateChangelog(entries);
846
- const outputPath = flags.output ?? "CHANGELOG.md";
847
- if (outputPath === "-") console.log(output);
848
- else await run(`Writing ${outputPath}`, () => writeFile(join(root, outputPath), output, "utf8"));
933
+ process.stdout.write(this.formatEntry(entry));
849
934
  }
850
935
  });
851
936
  };
852
937
 
853
938
  //#endregion
854
- //#region ../../src/cli/commands/CoreCommands.ts
855
- var CoreCommands = class {
856
- log = $logger();
857
- cli = $inject(CliProvider);
858
- utils = $inject(AlephaCliUtils);
859
- /**
860
- * Called when no command is provided
861
- */
862
- root = $command({
863
- root: true,
864
- flags: t.object({ version: t.optional(t.boolean({
865
- description: "Show Alepha CLI version",
866
- aliases: ["v"]
867
- })) }),
868
- handler: async ({ flags }) => {
869
- if (flags.version) {
870
- this.log.info(version);
871
- return;
872
- }
873
- this.cli.printHelp();
874
- }
875
- });
939
+ //#region ../../src/cli/commands/clean.ts
940
+ var CleanCommand = class {
876
941
  /**
877
942
  * Clean the project, removing the "dist" directory
878
943
  */
@@ -883,70 +948,22 @@ var CoreCommands = class {
883
948
  await run.rm("./dist");
884
949
  }
885
950
  });
951
+ };
952
+
953
+ //#endregion
954
+ //#region ../../src/cli/commands/db.ts
955
+ const drizzleCommandFlags = t.object({
956
+ provider: t.optional(t.text({ description: "Database provider name to target (e.g., 'postgres', 'sqlite')" })),
957
+ mode: t.optional(t.text({ description: "Environment variable file(s) to load (e.g., 'production' to load .env.production) https://vite.dev/guide/env-and-mode" }))
958
+ });
959
+ var DbCommand = class {
960
+ log = $logger();
961
+ utils = $inject(AlephaCliUtils);
886
962
  /**
887
- * Ensure the project has the necessary Alepha configuration files.
888
- * Add the correct dependencies to package.json and install them.
889
- */
890
- init = $command({
891
- name: "init",
892
- description: "Add missing Alepha configuration files to the project",
893
- flags: t.object({
894
- yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
895
- pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
896
- npm: t.optional(t.boolean({ description: "Use npm package manager" })),
897
- bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
898
- react: t.optional(t.boolean({ description: "Include Alepha React dependencies" })),
899
- ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
900
- test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
901
- }),
902
- handler: async ({ run, flags, root }) => {
903
- if (flags.ui) flags.react = true;
904
- const isExpo = await this.utils.hasExpo(root);
905
- await run({
906
- name: "ensuring configuration files",
907
- handler: async () => {
908
- await this.utils.ensureConfig(root, {
909
- tsconfigJson: true,
910
- packageJson: flags,
911
- biomeJson: true,
912
- viteConfigTs: !isExpo,
913
- editorconfig: true,
914
- indexHtml: !!flags.react && !isExpo
915
- });
916
- if (!flags.react) await this.utils.ensureSrcMain(root);
917
- }
918
- });
919
- const pm = await this.utils.getPackageManager(root, flags);
920
- if (pm === "yarn") {
921
- await this.utils.ensureYarn(root);
922
- await run("yarn set version stable");
923
- } else if (pm === "pnpm") await this.utils.ensurePnpm(root);
924
- else await this.utils.ensureNpm(root);
925
- await run(`${pm} install`, { alias: `installing dependencies with ${pm}` });
926
- if (!isExpo) await this.utils.ensureDependency(root, "vite", { run });
927
- await this.utils.ensureDependency(root, "@biomejs/biome", { run });
928
- if (flags.test) {
929
- await this.utils.ensureTestDir(root);
930
- await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
931
- }
932
- }
933
- });
934
- };
935
-
936
- //#endregion
937
- //#region ../../src/cli/commands/DrizzleCommands.ts
938
- const drizzleCommandFlags = t.object({
939
- provider: t.optional(t.text({ description: "Database provider name to target (e.g., 'postgres', 'sqlite')" })),
940
- mode: t.optional(t.text({ description: "Environment variable file(s) to load (e.g., 'production' to load .env.production) https://vite.dev/guide/env-and-mode" }))
941
- });
942
- var DrizzleCommands = class {
943
- log = $logger();
944
- utils = $inject(AlephaCliUtils);
945
- /**
946
- * Check if database migrations are up to date.
963
+ * Check if database migrations are up to date.
947
964
  */
948
965
  check = $command({
949
- name: "db:check-migrations",
966
+ name: "check-migrations",
950
967
  description: "Check if database migration files are up to date",
951
968
  args: t.optional(t.text({
952
969
  title: "path",
@@ -968,7 +985,7 @@ var DrizzleCommands = class {
968
985
  const migrationDir = join(rootDir, "migrations", providerName);
969
986
  const journalFile = await readFile(`${migrationDir}/meta/_journal.json`, "utf-8").catch(() => null);
970
987
  if (!journalFile) {
971
- this.log.info(`No migration journal found.`);
988
+ this.log.info("No migration journal found.");
972
989
  return;
973
990
  }
974
991
  const journal = JSON.parse(journalFile);
@@ -1005,15 +1022,9 @@ var DrizzleCommands = class {
1005
1022
  });
1006
1023
  /**
1007
1024
  * Generate database migration files
1008
- *
1009
- * - Loads the Alepha instance from the specified entry file.
1010
- * - Retrieves all repository primitives to gather database models.
1011
- * - Creates temporary entity definitions based on the current database schema.
1012
- * - Writes these definitions to a temporary schema file. (node_modules/.db/entities.ts)
1013
- * - Invokes Drizzle Kit's CLI to generate migration files based on the current schema.
1014
1025
  */
1015
1026
  generate = $command({
1016
- name: "db:generate",
1027
+ name: "generate",
1017
1028
  description: "Generate migration files based on current database schema",
1018
1029
  summary: false,
1019
1030
  args: t.optional(t.text({
@@ -1036,14 +1047,9 @@ var DrizzleCommands = class {
1036
1047
  });
1037
1048
  /**
1038
1049
  * Push database schema changes directly to the database
1039
- *
1040
- * - Loads the Alepha instance from the specified entry file.
1041
- * - Retrieves all repository primitives to gather database models.
1042
- * - Creates temporary entity definitions and Drizzle config.
1043
- * - Invokes Drizzle Kit's push command to apply schema changes directly.
1044
1050
  */
1045
1051
  push = $command({
1046
- name: "db:push",
1052
+ name: "push",
1047
1053
  description: "Push database schema changes directly to the database",
1048
1054
  summary: false,
1049
1055
  args: t.optional(t.text({
@@ -1064,14 +1070,9 @@ var DrizzleCommands = class {
1064
1070
  });
1065
1071
  /**
1066
1072
  * Apply pending database migrations
1067
- *
1068
- * - Loads the Alepha instance from the specified entry file.
1069
- * - Retrieves all repository primitives to gather database models.
1070
- * - Creates temporary entity definitions and Drizzle config.
1071
- * - Invokes Drizzle Kit's migrate command to apply pending migrations.
1072
1073
  */
1073
1074
  migrate = $command({
1074
- name: "db:migrate",
1075
+ name: "migrate",
1075
1076
  description: "Apply pending database migrations",
1076
1077
  summary: false,
1077
1078
  args: t.optional(t.text({
@@ -1092,14 +1093,9 @@ var DrizzleCommands = class {
1092
1093
  });
1093
1094
  /**
1094
1095
  * Launch Drizzle Studio database browser
1095
- *
1096
- * - Loads the Alepha instance from the specified entry file.
1097
- * - Retrieves all repository primitives to gather database models.
1098
- * - Creates temporary entity definitions and Drizzle config.
1099
- * - Invokes Drizzle Kit's studio command to launch the web-based database browser.
1100
1096
  */
1101
1097
  studio = $command({
1102
- name: "db:studio",
1098
+ name: "studio",
1103
1099
  description: "Launch Drizzle Studio database browser",
1104
1100
  summary: false,
1105
1101
  args: t.optional(t.text({
@@ -1119,18 +1115,30 @@ var DrizzleCommands = class {
1119
1115
  }
1120
1116
  });
1121
1117
  /**
1118
+ * Parent command for database operations.
1119
+ */
1120
+ db = $command({
1121
+ name: "db",
1122
+ description: "Database management commands",
1123
+ children: [
1124
+ this.check,
1125
+ this.generate,
1126
+ this.push,
1127
+ this.migrate,
1128
+ this.studio
1129
+ ],
1130
+ handler: async ({ help }) => {
1131
+ help();
1132
+ }
1133
+ });
1134
+ /**
1122
1135
  * Run a drizzle-kit command for all database providers in an Alepha instance.
1123
- *
1124
- * Iterates through all repository providers, prepares Drizzle config for each,
1125
- * and executes the specified drizzle-kit command.
1126
- *
1127
- * @param options - Configuration including command to run, flags, and logging
1128
1136
  */
1129
1137
  async runDrizzleKitCommand(options) {
1130
1138
  const rootDir = options.root;
1131
1139
  const envFiles = [".env"];
1132
1140
  if (options.env) envFiles.push(`.env.${options.env}`);
1133
- await this.utils.loadEnvFile(rootDir, envFiles);
1141
+ await this.utils.loadEnv(rootDir, envFiles);
1134
1142
  this.log.debug(`Using project root: ${rootDir}`);
1135
1143
  const { alepha, entry } = await this.utils.loadAlephaFromServerEntryFile(rootDir, options.args);
1136
1144
  const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
@@ -1163,12 +1171,6 @@ var DrizzleCommands = class {
1163
1171
  }
1164
1172
  /**
1165
1173
  * Prepare Drizzle configuration files for a database provider.
1166
- *
1167
- * Creates temporary entities.js and drizzle.config.js files needed
1168
- * for Drizzle Kit commands to run properly.
1169
- *
1170
- * @param options - Configuration options including kit, provider info, and paths
1171
- * @returns Path to the generated drizzle.config.js file
1172
1174
  */
1173
1175
  async prepareDrizzleConfig(options) {
1174
1176
  const models = Object.keys(options.kit.getModels(options.provider));
@@ -1179,6 +1181,7 @@ var DrizzleCommands = class {
1179
1181
  dialect: options.dialect,
1180
1182
  dbCredentials: { url: options.providerUrl }
1181
1183
  };
1184
+ if (options.provider.schema) config.schemaFilter = options.provider.schema;
1182
1185
  if (options.providerName === "d1") config.driver = "d1-http";
1183
1186
  if (options.providerName === "pglite") config.driver = "pglite";
1184
1187
  if (options.dialect === "sqlite") if (options.providerName === "d1") {
@@ -1207,73 +1210,91 @@ var DrizzleCommands = class {
1207
1210
  };
1208
1211
 
1209
1212
  //#endregion
1210
- //#region ../../src/cli/commands/VerifyCommands.ts
1211
- var VerifyCommands = class {
1213
+ //#region ../../src/cli/commands/deploy.ts
1214
+ var DeployCommand = class {
1215
+ log = $logger();
1212
1216
  utils = $inject(AlephaCliUtils);
1213
1217
  /**
1214
- * Run a series of verification commands to ensure code quality and correctness.
1218
+ * Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
1215
1219
  *
1216
- * This command runs the following checks in order:
1217
- * - Clean the project
1218
- * - Format the code
1219
- * - Lint the code
1220
- * - Run tests (if Vitest is a dev dependency)
1221
- * - Check database migrations (if a migrations directory exists)
1222
- * - Type check the code
1223
- * - Build the project
1224
- * - Clean the project again
1225
- */
1226
- verify = $command({
1227
- name: "verify",
1228
- description: "Verify the Alepha project",
1229
- handler: async ({ root, run }) => {
1230
- await run("alepha clean");
1231
- await run("alepha format");
1232
- await run("alepha lint");
1233
- await run("alepha typecheck");
1234
- if ((await this.utils.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
1235
- if (await this.utils.exists(root, "migrations")) await run("alepha db:check-migrations");
1236
- if (!await this.utils.hasExpo(root)) await run("alepha build");
1237
- await run("alepha clean");
1238
- }
1239
- });
1240
- /**
1241
- * Run TypeScript type checking across the codebase with no emit.
1220
+ * Deploy command can be overridden by creating a alepha.config.ts in the project root:
1221
+ *
1222
+ * ```ts
1223
+ * import { defineConfig } from "alepha/cli";
1224
+ *
1225
+ * export default defineConfig({
1226
+ * commands: {
1227
+ * deploy: {
1228
+ * handler: async ({ root, mode, flags }) => {
1229
+ * // Custom deployment logic here
1230
+ * },
1231
+ * },
1232
+ * },
1233
+ * });
1234
+ * ```
1242
1235
  */
1243
- typecheck = $command({
1244
- name: "typecheck",
1245
- description: "Check TypeScript types across the codebase",
1246
- handler: async ({ root }) => {
1247
- await this.utils.ensureDependency(root, "typescript");
1248
- await this.utils.exec("tsc --noEmit");
1236
+ deploy = $command({
1237
+ name: "deploy",
1238
+ description: "Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)",
1239
+ mode: true,
1240
+ flags: t.object({
1241
+ build: t.boolean({
1242
+ description: "Build the project before deployment",
1243
+ default: false
1244
+ }),
1245
+ migrate: t.boolean({
1246
+ description: "Run database migrations before deployment (if applicable)",
1247
+ default: false
1248
+ })
1249
+ }),
1250
+ env: t.object({
1251
+ VERCEL_TOKEN: t.optional(t.text({ description: "Vercel API token (e.g., xxxxxxxxxxxxxxxxxxxx)" })),
1252
+ VERCEL_ORG_ID: t.optional(t.text({ description: "Vercel organization ID (e.g., team_abc123...)" })),
1253
+ VERCEL_PROJECT_ID: t.optional(t.text({ description: "Vercel project ID (e.g., prj_abc123...)" })),
1254
+ CLOUDFLARE_API_TOKEN: t.optional(t.text({ description: "Cloudflare API token (e.g., xxxx-xxxx-xxxx-xxxx)" })),
1255
+ CLOUDFLARE_ACCOUNT_ID: t.optional(t.text({ description: "Cloudflare account ID (e.g., abc123def456...)" }))
1256
+ }),
1257
+ handler: async ({ root, mode, flags }) => {
1258
+ if (flags.build) await this.utils.exec("alepha build");
1259
+ if (await this.utils.exists(root, "dist/vercel.json")) {
1260
+ if (flags.migrate) {
1261
+ this.log.debug("Running database migrations before deployment...");
1262
+ await this.utils.exec(`alepha db migrate --mode=${mode}`);
1263
+ }
1264
+ await this.utils.ensureDependency(root, "vercel", { dev: true });
1265
+ const command = `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
1266
+ this.log.debug(`Deploying to Vercel with command: ${command}`);
1267
+ await this.utils.exec(command);
1268
+ return;
1269
+ }
1270
+ if (await this.utils.exists(root, "dist/wrangler.jsonc")) {
1271
+ if (flags.migrate) {
1272
+ this.log.debug("Running database migrations before deployment...");
1273
+ await this.utils.exec(`alepha db migrate --mode=${mode}`);
1274
+ }
1275
+ await this.utils.ensureDependency(root, "wrangler", { dev: true });
1276
+ const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
1277
+ this.log.info(`Deploying to Cloudflare with command: ${command}`);
1278
+ await this.utils.exec(command);
1279
+ return;
1280
+ }
1281
+ if (await this.utils.exists(root, "dist/public/404.html")) {
1282
+ await this.utils.ensureDependency(root, "surge", { dev: true });
1283
+ const distPath = join(root, "dist/public");
1284
+ this.log.debug(`Deploying to Surge from directory: ${distPath}`);
1285
+ await this.utils.exec(`surge ${distPath}`);
1286
+ return;
1287
+ }
1288
+ throw new AlephaError("No deployment configuration found in the dist folder.");
1249
1289
  }
1250
1290
  });
1251
1291
  };
1252
1292
 
1253
1293
  //#endregion
1254
- //#region ../../src/cli/commands/ViteCommands.ts
1255
- var ViteCommands = class {
1294
+ //#region ../../src/cli/commands/dev.ts
1295
+ var DevCommand = class {
1256
1296
  log = $logger();
1257
1297
  utils = $inject(AlephaCliUtils);
1258
- env = $env(t.object({ VITEST_ARGS: t.string({ default: "" }) }));
1259
- run = $command({
1260
- name: "run",
1261
- hide: true,
1262
- description: "Run a TypeScript file directly",
1263
- flags: t.object({ watch: t.optional(t.boolean({
1264
- description: "Watch file for changes",
1265
- alias: "w"
1266
- })) }),
1267
- summary: false,
1268
- args: t.text({
1269
- title: "path",
1270
- description: "Filepath to run"
1271
- }),
1272
- handler: async ({ args, flags, root }) => {
1273
- await this.utils.ensureTsConfig(root);
1274
- await this.utils.exec(`tsx ${flags.watch ? "watch " : ""}${args}`);
1275
- }
1276
- });
1277
1298
  /**
1278
1299
  * Will run the project in watch mode.
1279
1300
  *
@@ -1294,7 +1315,7 @@ var ViteCommands = class {
1294
1315
  tsconfigJson: true
1295
1316
  });
1296
1317
  if (expo) {
1297
- await this.utils.exec(`expo start`);
1318
+ await this.utils.exec("expo start");
1298
1319
  return;
1299
1320
  }
1300
1321
  const entry = await boot.getServerEntry(root, args);
@@ -1304,152 +1325,222 @@ var ViteCommands = class {
1304
1325
  } catch {
1305
1326
  this.log.trace("No index.html found, running entry file with tsx");
1306
1327
  let cmd = "tsx --watch";
1307
- if (await this.utils.exists(root, ".env")) cmd += ` --env-file=./.env`;
1328
+ if (await this.utils.exists(root, ".env")) cmd += " --env-file=./.env";
1308
1329
  cmd += ` ${entry}`;
1309
1330
  await this.utils.exec(cmd);
1310
1331
  return;
1311
1332
  }
1312
1333
  await this.utils.ensureDependency(root, "vite");
1313
- await this.utils.exec(`vite`);
1334
+ await this.utils.exec("vite");
1314
1335
  }
1315
1336
  });
1316
- build = $command({
1317
- name: "build",
1318
- description: "Build the project for production",
1319
- args: t.optional(t.text({
1320
- title: "path",
1321
- description: "Filepath to build"
1322
- })),
1337
+ };
1338
+
1339
+ //#endregion
1340
+ //#region ../../src/cli/commands/format.ts
1341
+ var FormatCommand = class {
1342
+ utils = $inject(AlephaCliUtils);
1343
+ format = $command({
1344
+ name: "format",
1345
+ description: "Format the codebase using Biome",
1346
+ handler: async ({ root }) => {
1347
+ await this.utils.ensureConfig(root, { biomeJson: true });
1348
+ await this.utils.ensureDependency(root, "@biomejs/biome");
1349
+ await this.utils.exec("biome format --fix");
1350
+ }
1351
+ });
1352
+ };
1353
+
1354
+ //#endregion
1355
+ //#region ../../src/cli/commands/init.ts
1356
+ var InitCommand = class {
1357
+ utils = $inject(AlephaCliUtils);
1358
+ /**
1359
+ * Ensure the project has the necessary Alepha configuration files.
1360
+ * Add the correct dependencies to package.json and install them.
1361
+ */
1362
+ init = $command({
1363
+ name: "init",
1364
+ description: "Add missing Alepha configuration files to the project",
1323
1365
  flags: t.object({
1324
- stats: t.optional(t.boolean({ description: "Generate build stats report" })),
1325
- vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
1326
- cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
1327
- docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
1328
- sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
1329
- prerender: t.optional(t.boolean({ description: "Pre-render static pages" }))
1366
+ yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
1367
+ pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
1368
+ npm: t.optional(t.boolean({ description: "Use npm package manager" })),
1369
+ bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
1370
+ react: t.optional(t.boolean({ description: "Include Alepha React dependencies" })),
1371
+ ui: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
1372
+ test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
1330
1373
  }),
1331
- handler: async ({ flags, args, run, root }) => {
1332
- process.env.ALEPHA_BUILD_MODE = "cli";
1333
- process.env.NODE_ENV = "production";
1334
- if (await this.utils.hasExpo(root)) return;
1335
- await this.utils.ensureConfig(root, {
1336
- viteConfigTs: true,
1337
- tsconfigJson: true
1338
- });
1339
- const entry = await boot.getServerEntry(root, args);
1340
- this.log.trace("Entry file found", { entry });
1341
- const distDir = "dist";
1342
- const clientDir = "public";
1343
- await this.utils.ensureDependency(root, "vite", { run });
1344
- await run.rm("dist", { alias: "clean dist" });
1345
- const viteAlephaBuildOptions = (await createRequire(import.meta.url)("vite").resolveConfig({}, "build", "production")).plugins.find((it) => it.name === "alepha:build")?.[OPTIONS] || {};
1346
- await this.utils.loadEnvFile(root, [".env", ".env.production"]);
1347
- const stats = flags.stats ?? viteAlephaBuildOptions.stats ?? false;
1348
- const hasServer = viteAlephaBuildOptions.serverEntry !== false;
1349
- let hasClient = false;
1350
- try {
1351
- await access(join(root, "index.html"));
1352
- hasClient = true;
1353
- } catch {}
1354
- const clientOptions = typeof viteAlephaBuildOptions.client === "object" ? viteAlephaBuildOptions.client : {};
1355
- if (hasClient) await run({
1356
- name: "vite build client",
1357
- handler: () => buildClient({
1358
- silent: true,
1359
- dist: `${distDir}/${clientDir}`,
1360
- stats,
1361
- precompress: clientOptions.precompress
1362
- })
1363
- });
1374
+ handler: async ({ run, flags, root }) => {
1375
+ if (flags.ui) flags.react = true;
1376
+ const isExpo = await this.utils.hasExpo(root);
1364
1377
  await run({
1365
- name: "vite build server",
1378
+ name: "ensuring configuration files",
1366
1379
  handler: async () => {
1367
- let clientBuilt = false;
1368
- try {
1369
- await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
1370
- clientBuilt = true;
1371
- } catch {}
1372
- await buildServer({
1373
- silent: true,
1374
- entry,
1375
- distDir,
1376
- clientDir: clientBuilt ? clientDir : void 0,
1377
- stats
1380
+ await this.utils.ensureConfig(root, {
1381
+ tsconfigJson: true,
1382
+ packageJson: flags,
1383
+ biomeJson: true,
1384
+ viteConfigTs: !isExpo,
1385
+ editorconfig: true,
1386
+ indexHtml: !!flags.react && !isExpo
1378
1387
  });
1379
- if (clientBuilt && hasServer) await unlink(`${distDir}/${clientDir}/index.html`);
1388
+ if (!flags.react) await this.utils.ensureSrcMain(root);
1380
1389
  }
1381
1390
  });
1382
- await copyAssets({
1383
- root,
1384
- entry: `${distDir}/index.js`,
1385
- distDir,
1386
- run
1387
- });
1388
- if (hasClient) {
1389
- const sitemapBaseUrl = flags.sitemap ?? clientOptions.sitemap?.hostname;
1390
- if (sitemapBaseUrl) await run({
1391
- name: "add sitemap",
1392
- handler: async () => {
1393
- await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
1394
- entry: `${distDir}/index.js`,
1395
- baseUrl: sitemapBaseUrl
1396
- }));
1397
- }
1398
- });
1399
- if (flags.prerender ?? clientOptions.prerender) await run({
1400
- name: "pre-render pages",
1401
- handler: async () => {
1402
- await prerenderPages({
1403
- dist: `${distDir}/${clientDir}`,
1404
- entry: `${distDir}/index.js`,
1405
- compress: clientOptions.precompress
1406
- });
1407
- }
1408
- });
1409
- }
1410
- if (flags.vercel || viteAlephaBuildOptions.vercel) {
1411
- const config = typeof viteAlephaBuildOptions.vercel === "object" ? viteAlephaBuildOptions.vercel : {};
1412
- await run({
1413
- name: "add Vercel config",
1414
- handler: () => generateVercel({
1415
- distDir,
1416
- clientDir,
1417
- config
1418
- })
1419
- });
1420
- }
1421
- if (flags.cloudflare || viteAlephaBuildOptions.cloudflare) {
1422
- const config = typeof viteAlephaBuildOptions.cloudflare === "boolean" ? {} : viteAlephaBuildOptions.cloudflare;
1423
- await run({
1424
- name: "add Cloudflare config",
1425
- handler: () => generateCloudflare({
1426
- distDir,
1427
- config
1428
- })
1429
- });
1391
+ const pm = await this.utils.getPackageManager(root, flags);
1392
+ if (pm === "yarn") {
1393
+ await this.utils.ensureYarn(root);
1394
+ await run("yarn set version stable");
1395
+ } else if (pm === "pnpm") await this.utils.ensurePnpm(root);
1396
+ else await this.utils.ensureNpm(root);
1397
+ await run(`${pm} install`, { alias: `installing dependencies with ${pm}` });
1398
+ if (!isExpo) await this.utils.ensureDependency(root, "vite", { run });
1399
+ await this.utils.ensureDependency(root, "@biomejs/biome", { run });
1400
+ if (flags.test) {
1401
+ await this.utils.ensureTestDir(root);
1402
+ await run(`${pm} ${pm === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
1430
1403
  }
1431
- if (flags.docker || viteAlephaBuildOptions.docker) {
1432
- const dockerConfig = typeof viteAlephaBuildOptions.docker === "object" ? viteAlephaBuildOptions.docker : {};
1433
- await run({
1434
- name: "add Docker config",
1435
- handler: () => generateDocker({
1436
- distDir,
1437
- ...dockerConfig
1438
- })
1439
- });
1404
+ }
1405
+ });
1406
+ };
1407
+
1408
+ //#endregion
1409
+ //#region ../../src/cli/commands/lint.ts
1410
+ var LintCommand = class {
1411
+ utils = $inject(AlephaCliUtils);
1412
+ lint = $command({
1413
+ name: "lint",
1414
+ description: "Run linter across the codebase using Biome",
1415
+ handler: async ({ root }) => {
1416
+ await this.utils.ensureConfig(root, { biomeJson: true });
1417
+ await this.utils.ensureDependency(root, "@biomejs/biome");
1418
+ await this.utils.exec("biome check --formatter-enabled=false --fix");
1419
+ }
1420
+ });
1421
+ };
1422
+
1423
+ //#endregion
1424
+ //#region ../../src/cli/commands/root.ts
1425
+ var RootCommand = class {
1426
+ log = $logger();
1427
+ cli = $inject(CliProvider);
1428
+ /**
1429
+ * Called when no command is provided
1430
+ */
1431
+ root = $command({
1432
+ root: true,
1433
+ flags: t.object({ version: t.optional(t.boolean({
1434
+ description: "Show Alepha CLI version",
1435
+ aliases: ["v"]
1436
+ })) }),
1437
+ handler: async ({ flags }) => {
1438
+ if (flags.version) {
1439
+ this.log.info(version);
1440
+ return;
1440
1441
  }
1442
+ this.cli.printHelp();
1441
1443
  }
1442
1444
  });
1445
+ };
1446
+
1447
+ //#endregion
1448
+ //#region ../../src/cli/commands/run.ts
1449
+ var RunCommand = class {
1450
+ utils = $inject(AlephaCliUtils);
1451
+ run = $command({
1452
+ name: "run",
1453
+ hide: true,
1454
+ description: "Run a TypeScript file directly",
1455
+ flags: t.object({ watch: t.optional(t.boolean({
1456
+ description: "Watch file for changes",
1457
+ alias: "w"
1458
+ })) }),
1459
+ summary: false,
1460
+ args: t.text({
1461
+ title: "path",
1462
+ description: "Filepath to run"
1463
+ }),
1464
+ handler: async ({ args, flags, root }) => {
1465
+ await this.utils.ensureTsConfig(root);
1466
+ await this.utils.exec(`tsx ${flags.watch ? "watch " : ""}${args}`);
1467
+ }
1468
+ });
1469
+ };
1470
+
1471
+ //#endregion
1472
+ //#region ../../src/cli/commands/test.ts
1473
+ var TestCommand = class {
1474
+ utils = $inject(AlephaCliUtils);
1443
1475
  test = $command({
1444
1476
  name: "test",
1445
1477
  description: "Run tests using Vitest",
1446
- handler: async ({ root }) => {
1478
+ flags: t.object({ config: t.optional(t.string({
1479
+ description: "Path to Vitest config file",
1480
+ alias: "c"
1481
+ })) }),
1482
+ env: t.object({ VITEST_ARGS: t.optional(t.string({
1483
+ default: "",
1484
+ description: "Additional arguments to pass to Vitest. E.g., --coverage"
1485
+ })) }),
1486
+ handler: async ({ root, flags, env }) => {
1447
1487
  await this.utils.ensureConfig(root, {
1448
1488
  tsconfigJson: true,
1449
1489
  viteConfigTs: true
1450
1490
  });
1451
1491
  await this.utils.ensureDependency(root, "vitest");
1452
- await this.utils.exec(`vitest run ${this.env.VITEST_ARGS}`);
1492
+ const config = flags.config ? `--config=${flags.config}` : "";
1493
+ await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
1494
+ }
1495
+ });
1496
+ };
1497
+
1498
+ //#endregion
1499
+ //#region ../../src/cli/commands/typecheck.ts
1500
+ var TypecheckCommand = class {
1501
+ utils = $inject(AlephaCliUtils);
1502
+ /**
1503
+ * Run TypeScript type checking across the codebase with no emit.
1504
+ */
1505
+ typecheck = $command({
1506
+ name: "typecheck",
1507
+ description: "Check TypeScript types across the codebase",
1508
+ handler: async ({ root }) => {
1509
+ await this.utils.ensureDependency(root, "typescript");
1510
+ await this.utils.exec("tsc --noEmit");
1511
+ }
1512
+ });
1513
+ };
1514
+
1515
+ //#endregion
1516
+ //#region ../../src/cli/commands/verify.ts
1517
+ var VerifyCommand = class {
1518
+ utils = $inject(AlephaCliUtils);
1519
+ /**
1520
+ * Run a series of verification commands to ensure code quality and correctness.
1521
+ *
1522
+ * This command runs the following checks in order:
1523
+ * - Clean the project
1524
+ * - Format the code
1525
+ * - Lint the code
1526
+ * - Run tests (if Vitest is a dev dependency)
1527
+ * - Check database migrations (if a migrations directory exists)
1528
+ * - Type check the code
1529
+ * - Build the project
1530
+ * - Clean the project again
1531
+ */
1532
+ verify = $command({
1533
+ name: "verify",
1534
+ description: "Verify the Alepha project",
1535
+ handler: async ({ root, run }) => {
1536
+ await run("alepha clean");
1537
+ await run("alepha format");
1538
+ await run("alepha lint");
1539
+ await run("alepha typecheck");
1540
+ if ((await this.utils.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
1541
+ if (await this.utils.exists(root, "migrations")) await run("alepha db:check-migrations");
1542
+ if (!await this.utils.hasExpo(root)) await run("alepha build");
1543
+ await run("alepha clean");
1453
1544
  }
1454
1545
  });
1455
1546
  };
@@ -1466,7 +1557,7 @@ var AlephaCliExtension = class {
1466
1557
  if (!await this.fs.exists(extensionPath)) return;
1467
1558
  const { default: Extension } = await import(extensionPath);
1468
1559
  if (typeof Extension !== "function") return;
1469
- this.alepha.with(Extension);
1560
+ this.alepha.inject(Extension, { args: [this.alepha] });
1470
1561
  }
1471
1562
  });
1472
1563
  };
@@ -1474,12 +1565,21 @@ const AlephaCli = $module({
1474
1565
  name: "alepha.cli",
1475
1566
  services: [
1476
1567
  AlephaCliExtension,
1477
- ChangelogCommands,
1478
- CoreCommands,
1479
- DrizzleCommands,
1480
- VerifyCommands,
1481
- ViteCommands,
1482
- BiomeCommands
1568
+ BuildCommand,
1569
+ ChangelogCommand,
1570
+ CleanCommand,
1571
+ DbCommand,
1572
+ DeployCommand,
1573
+ DevCommand,
1574
+ FormatCommand,
1575
+ InitCommand,
1576
+ LintCommand,
1577
+ RootCommand,
1578
+ RunCommand,
1579
+ TestCommand,
1580
+ TypecheckCommand,
1581
+ VerifyCommand,
1582
+ GitProvider
1483
1583
  ]
1484
1584
  });
1485
1585
 
@@ -1516,13 +1616,18 @@ var AlephaPackageBuilderCli = class {
1516
1616
  pkgData.exports["./tsconfig.base"] = "./tsconfig.base.json";
1517
1617
  pkgData.exports["./package.json"] = "./package.json";
1518
1618
  }
1519
- if (packageName === "@alepha/ui") pkgData.exports["./styles"] = "./styles.css";
1619
+ if (packageName === "@alepha/ui") {
1620
+ pkgData.exports["./styles"] = "./src/core/styles.css";
1621
+ pkgData.exports["./json/styles"] = "./src/json/styles.css";
1622
+ }
1520
1623
  await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
1521
1624
  const tmpDir = join(root, "node_modules/.alepha");
1522
1625
  await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
1523
1626
  await this.fs.writeFile(join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
1524
1627
  const tsconfig = await readFile(join(root, "../../tsconfig.json"), "utf-8");
1525
1628
  const external = Object.keys(JSON.parse(tsconfig).compilerOptions.paths);
1629
+ external.push("bun");
1630
+ external.push("bun:sqlite");
1526
1631
  await run.rm(this.dist);
1527
1632
  const build = async (item) => {
1528
1633
  const entries = [];
@@ -1672,5 +1777,19 @@ async function analyzeModules(srcDir, packageName) {
1672
1777
  }
1673
1778
 
1674
1779
  //#endregion
1675
- export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BiomeCommands, CoreCommands, DrizzleCommands, VerifyCommands, ViteCommands, analyzeModules, version };
1780
+ //#region ../../src/cli/defineConfig.ts
1781
+ const defineConfig = (config) => {
1782
+ return (alepha) => {
1783
+ const { commands, services = [] } = config(alepha);
1784
+ for (const it of services) alepha.with(it);
1785
+ return { ...commands };
1786
+ };
1787
+ };
1788
+ /**
1789
+ * @alias defineConfig
1790
+ */
1791
+ const defineAlephaConfig = defineConfig;
1792
+
1793
+ //#endregion
1794
+ export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, FormatCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, RootCommand, RunCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
1676
1795
  //# sourceMappingURL=index.js.map