alepha 0.19.2 → 0.19.4

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 (241) hide show
  1. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  2. package/dist/api/audits/index.d.ts +8 -8
  3. package/dist/api/invitations/index.d.ts +790 -0
  4. package/dist/api/invitations/index.d.ts.map +1 -0
  5. package/dist/api/invitations/index.js +665 -0
  6. package/dist/api/invitations/index.js.map +1 -0
  7. package/dist/api/jobs/index.browser.js +8 -9
  8. package/dist/api/jobs/index.browser.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +90 -34
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js +267 -44
  12. package/dist/api/jobs/index.js.map +1 -1
  13. package/dist/api/notifications/index.browser.js +0 -1
  14. package/dist/api/notifications/index.browser.js.map +1 -1
  15. package/dist/api/notifications/index.d.ts +3 -3
  16. package/dist/api/notifications/index.d.ts.map +1 -1
  17. package/dist/api/notifications/index.js +0 -1
  18. package/dist/api/notifications/index.js.map +1 -1
  19. package/dist/api/parameters/index.browser.js +112 -1
  20. package/dist/api/parameters/index.browser.js.map +1 -1
  21. package/dist/api/parameters/index.d.ts +90 -3
  22. package/dist/api/parameters/index.d.ts.map +1 -1
  23. package/dist/api/parameters/index.js +79 -12
  24. package/dist/api/parameters/index.js.map +1 -1
  25. package/dist/{billing → api/payments}/index.d.ts +67 -49
  26. package/dist/api/payments/index.d.ts.map +1 -0
  27. package/dist/{billing → api/payments}/index.js +108 -74
  28. package/dist/api/payments/index.js.map +1 -0
  29. package/dist/api/subscriptions/index.d.ts +1692 -0
  30. package/dist/api/subscriptions/index.d.ts.map +1 -0
  31. package/dist/api/subscriptions/index.js +1870 -0
  32. package/dist/api/subscriptions/index.js.map +1 -0
  33. package/dist/api/users/index.d.ts +27 -21
  34. package/dist/api/users/index.d.ts.map +1 -1
  35. package/dist/api/users/index.js +167 -34
  36. package/dist/api/users/index.js.map +1 -1
  37. package/dist/api/workflows/index.browser.js +246 -0
  38. package/dist/api/workflows/index.browser.js.map +1 -0
  39. package/dist/api/workflows/index.d.ts +1618 -0
  40. package/dist/api/workflows/index.d.ts.map +1 -0
  41. package/dist/api/workflows/index.js +1504 -0
  42. package/dist/api/workflows/index.js.map +1 -0
  43. package/dist/cli/config/index.d.ts +6 -28
  44. package/dist/cli/config/index.d.ts.map +1 -1
  45. package/dist/cli/config/index.js +5 -10
  46. package/dist/cli/config/index.js.map +1 -1
  47. package/dist/cli/core/index.d.ts +11669 -208
  48. package/dist/cli/core/index.d.ts.map +1 -1
  49. package/dist/cli/core/index.js +60 -69
  50. package/dist/cli/core/index.js.map +1 -1
  51. package/dist/cli/devtools/index.d.ts +5 -0
  52. package/dist/cli/devtools/index.d.ts.map +1 -1
  53. package/dist/cli/devtools/index.js +4 -0
  54. package/dist/cli/devtools/index.js.map +1 -1
  55. package/dist/cli/platform/index.d.ts +69 -64
  56. package/dist/cli/platform/index.d.ts.map +1 -1
  57. package/dist/cli/platform/index.js +6 -2
  58. package/dist/cli/platform/index.js.map +1 -1
  59. package/dist/cli/vendor/index.d.ts +38 -10
  60. package/dist/cli/vendor/index.d.ts.map +1 -1
  61. package/dist/cli/vendor/index.js +85 -26
  62. package/dist/cli/vendor/index.js.map +1 -1
  63. package/dist/core/index.browser.js +21 -2
  64. package/dist/core/index.browser.js.map +1 -1
  65. package/dist/core/index.d.ts +33 -2
  66. package/dist/core/index.d.ts.map +1 -1
  67. package/dist/core/index.js +25 -2
  68. package/dist/core/index.js.map +1 -1
  69. package/dist/core/index.native.js +25 -2
  70. package/dist/core/index.native.js.map +1 -1
  71. package/dist/core/index.workerd.js +25 -2
  72. package/dist/core/index.workerd.js.map +1 -1
  73. package/dist/email/smtp/index.js +24 -8
  74. package/dist/email/smtp/index.js.map +1 -1
  75. package/dist/logger/index.d.ts.map +1 -1
  76. package/dist/logger/index.js +1 -1
  77. package/dist/logger/index.js.map +1 -1
  78. package/dist/orm/core/index.browser.js +0 -18
  79. package/dist/orm/core/index.browser.js.map +1 -1
  80. package/dist/orm/core/index.bun.js +25 -73
  81. package/dist/orm/core/index.bun.js.map +1 -1
  82. package/dist/orm/core/index.d.ts +10 -32
  83. package/dist/orm/core/index.d.ts.map +1 -1
  84. package/dist/orm/core/index.js +25 -73
  85. package/dist/orm/core/index.js.map +1 -1
  86. package/dist/orm/postgres/index.bun.js +3 -3
  87. package/dist/orm/postgres/index.bun.js.map +1 -1
  88. package/dist/orm/postgres/index.d.ts +2 -1
  89. package/dist/orm/postgres/index.d.ts.map +1 -1
  90. package/dist/orm/postgres/index.js +3 -3
  91. package/dist/orm/postgres/index.js.map +1 -1
  92. package/dist/react/router/index.browser.js +25 -3
  93. package/dist/react/router/index.browser.js.map +1 -1
  94. package/dist/react/router/index.d.ts +16 -1
  95. package/dist/react/router/index.d.ts.map +1 -1
  96. package/dist/react/router/index.js +25 -3
  97. package/dist/react/router/index.js.map +1 -1
  98. package/dist/security/index.d.ts +28 -0
  99. package/dist/security/index.d.ts.map +1 -1
  100. package/dist/security/index.js +28 -0
  101. package/dist/security/index.js.map +1 -1
  102. package/package.json +37 -20
  103. package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
  104. package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
  105. package/src/api/invitations/controllers/InvitationController.ts +84 -0
  106. package/src/api/invitations/entities/invitations.ts +33 -0
  107. package/src/api/invitations/index.ts +65 -0
  108. package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
  109. package/src/api/invitations/providers/InvitationProvider.ts +45 -0
  110. package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
  111. package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
  112. package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
  113. package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
  114. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
  115. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
  116. package/src/api/invitations/services/InvitationService.ts +556 -0
  117. package/src/api/jobs/__tests__/$job.spec.ts +876 -0
  118. package/src/api/jobs/controllers/AdminJobController.ts +44 -0
  119. package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
  120. package/src/api/jobs/index.ts +0 -3
  121. package/src/api/jobs/primitives/$job.ts +22 -11
  122. package/src/api/jobs/providers/JobProvider.ts +239 -25
  123. package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
  124. package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
  125. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
  126. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
  127. package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
  128. package/src/api/jobs/services/JobService.ts +51 -12
  129. package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
  130. package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
  131. package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
  132. package/src/api/parameters/index.browser.ts +12 -0
  133. package/src/api/parameters/primitives/$parameter.ts +20 -3
  134. package/src/api/parameters/services/ParameterProvider.ts +48 -7
  135. package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
  136. package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
  137. package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
  138. package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
  139. package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
  140. package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
  141. package/src/{billing → api/payments}/index.ts +31 -25
  142. package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
  143. package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
  144. package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
  145. package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
  146. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  147. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  148. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  149. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  150. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  151. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  152. package/src/api/subscriptions/index.ts +144 -0
  153. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  154. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  155. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  156. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  157. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  158. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  159. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  160. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  161. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  162. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  163. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  164. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  165. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  166. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  167. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  168. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  169. package/src/api/subscriptions/services/BillingService.ts +437 -0
  170. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  171. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  172. package/src/api/subscriptions/services/UsageService.ts +118 -0
  173. package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
  174. package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
  175. package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
  176. package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
  177. package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
  178. package/src/api/users/__tests__/SessionService.spec.ts +142 -1
  179. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
  180. package/src/api/users/controllers/UserController.ts +3 -8
  181. package/src/api/users/notifications/UserNotifications.ts +23 -0
  182. package/src/api/users/schemas/loginSchema.ts +1 -1
  183. package/src/api/users/services/CredentialService.ts +51 -4
  184. package/src/api/users/services/RegistrationService.ts +38 -9
  185. package/src/api/users/services/SessionService.ts +62 -9
  186. package/src/api/users/services/UserService.ts +21 -12
  187. package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
  188. package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
  189. package/src/api/workflows/entities/workflowExecutions.ts +74 -0
  190. package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
  191. package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
  192. package/src/api/workflows/index.browser.ts +22 -0
  193. package/src/api/workflows/index.ts +124 -0
  194. package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
  195. package/src/api/workflows/primitives/$workflow.ts +202 -0
  196. package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
  197. package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
  198. package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
  199. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
  200. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
  201. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
  202. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
  203. package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
  204. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
  205. package/src/api/workflows/services/WorkflowService.ts +382 -0
  206. package/src/cli/config/defineConfig.ts +17 -46
  207. package/src/cli/core/providers/ViteDevServerProvider.ts +45 -3
  208. package/src/cli/core/services/PackageManagerUtils.ts +3 -1
  209. package/src/cli/core/services/ProjectScaffolder.ts +5 -5
  210. package/src/cli/core/templates/agentMd.ts +14 -5
  211. package/src/cli/core/templates/webAppRouterTs.ts +5 -58
  212. package/src/cli/devtools/index.ts +21 -1
  213. package/src/cli/platform/index.ts +23 -2
  214. package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
  215. package/src/cli/vendor/index.ts +20 -3
  216. package/src/cli/vendor/services/VendorService.ts +126 -27
  217. package/src/core/Alepha.ts +10 -0
  218. package/src/core/__tests__/TypeProvider.spec.ts +4 -2
  219. package/src/core/providers/SchemaValidator.ts +1 -1
  220. package/src/core/providers/TypeProvider.ts +46 -3
  221. package/src/logger/index.ts +6 -1
  222. package/src/orm/__tests__/enums.spec.ts +22 -29
  223. package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
  224. package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
  225. package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
  226. package/src/orm/core/providers/DrizzleKitProvider.ts +56 -105
  227. package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
  228. package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
  229. package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
  230. package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
  231. package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
  232. package/src/security/primitives/$secure.ts +28 -0
  233. package/tsconfig.base.json +0 -1
  234. package/dist/billing/index.d.ts.map +0 -1
  235. package/dist/billing/index.js.map +0 -1
  236. package/src/billing/__tests__/BillingService.spec.ts +0 -136
  237. /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
  238. /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
  239. /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
  240. /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
  241. /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
@@ -21,6 +21,38 @@ describe("VendorService", () => {
21
21
  return { service, shell, fs };
22
22
  };
23
23
 
24
+ /**
25
+ * Helper to create a test service with a stubbed clone that returns
26
+ * a known directory, plus stubs getCommitHash to return a fixed hash.
27
+ */
28
+ const createTestService = (shell: MemoryShellProvider) => {
29
+ class TestVendorService extends VendorService {
30
+ protected override async cloneRemote(): Promise<string> {
31
+ await shell.run(
32
+ "git clone --depth 1 --branch main --filter=blob:none remote /tmp/test-clone",
33
+ );
34
+ return "/tmp/test-clone";
35
+ }
36
+
37
+ protected override async cloneAtCommit(): Promise<string> {
38
+ return "/tmp/test-baseline";
39
+ }
40
+
41
+ protected override async getCommitHash(): Promise<string> {
42
+ return "abc123";
43
+ }
44
+ }
45
+
46
+ const alepha = Alepha.create()
47
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
48
+ .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
49
+
50
+ return {
51
+ service: alepha.inject(TestVendorService),
52
+ fs: alepha.inject(MemoryFileSystemProvider),
53
+ };
54
+ };
55
+
24
56
  describe("sync", () => {
25
57
  it("should clone the remote repository", async ({ expect }) => {
26
58
  const { service, shell, fs } = createTestEnv();
@@ -48,40 +80,23 @@ describe("VendorService", () => {
48
80
  });
49
81
 
50
82
  it("should remove local package dir before copying", async ({ expect }) => {
51
- const { service, shell, fs } = createTestEnv();
52
-
53
- /**
54
- * Since MemoryShellProvider does not actually clone, we simulate
55
- * by pre-populating the temp directory. We override cloneRemote
56
- * to return a known path.
57
- */
58
- class TestVendorService extends VendorService {
59
- protected override async cloneRemote(): Promise<string> {
60
- await shell.run(
61
- "git clone --depth 1 --branch main --filter=blob:none remote /tmp/test-clone",
62
- );
63
- return "/tmp/test-clone";
64
- }
65
- }
66
-
67
- const alepha = Alepha.create()
68
- .with({ provide: ShellProvider, use: MemoryShellProvider })
69
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
70
-
71
- const testService = alepha.inject(TestVendorService);
72
- const testFs = alepha.inject(MemoryFileSystemProvider);
83
+ const { service, fs } = createTestService(
84
+ Alepha.create()
85
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
86
+ .inject(MemoryShellProvider),
87
+ );
73
88
 
74
- await testFs.mkdir("/tmp/test-clone/packages/my-pkg", {
89
+ await fs.mkdir("/tmp/test-clone/packages/my-pkg", {
75
90
  recursive: true,
76
91
  });
77
- await testFs.writeFile(
92
+ await fs.writeFile(
78
93
  "/tmp/test-clone/packages/my-pkg/index.ts",
79
94
  "export {}",
80
95
  );
81
- await testFs.mkdir("/project/packages/my-pkg", { recursive: true });
82
- await testFs.writeFile("/project/packages/my-pkg/old-file.ts", "old");
96
+ await fs.mkdir("/project/packages/my-pkg", { recursive: true });
97
+ await fs.writeFile("/project/packages/my-pkg/old-file.ts", "old");
83
98
 
84
- const result = await testService.sync({
99
+ const result = await service.sync({
85
100
  root: "/project",
86
101
  remote: "remote",
87
102
  branch: "main",
@@ -91,28 +106,21 @@ describe("VendorService", () => {
91
106
 
92
107
  expect(result.synced).toEqual(["my-pkg"]);
93
108
  expect(result.errors).toEqual([]);
94
- expect(testFs.wasDeleted("/project/packages/my-pkg")).toBe(true);
109
+ expect(fs.wasDeleted("/project/packages/my-pkg")).toBe(true);
95
110
  });
96
111
 
97
112
  it("should report errors for missing remote packages", async ({
98
113
  expect,
99
114
  }) => {
100
- class TestVendorService extends VendorService {
101
- protected override async cloneRemote(): Promise<string> {
102
- return "/tmp/test-clone";
103
- }
104
- }
105
-
106
- const alepha = Alepha.create()
107
- .with({ provide: ShellProvider, use: MemoryShellProvider })
108
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
109
-
110
- const testService = alepha.inject(TestVendorService);
111
- const testFs = alepha.inject(MemoryFileSystemProvider);
115
+ const { service, fs } = createTestService(
116
+ Alepha.create()
117
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
118
+ .inject(MemoryShellProvider),
119
+ );
112
120
 
113
- await testFs.mkdir("/tmp/test-clone/packages", { recursive: true });
121
+ await fs.mkdir("/tmp/test-clone/packages", { recursive: true });
114
122
 
115
- const result = await testService.sync({
123
+ const result = await service.sync({
116
124
  root: "/project",
117
125
  remote: "remote",
118
126
  branch: "main",
@@ -126,25 +134,18 @@ describe("VendorService", () => {
126
134
  });
127
135
 
128
136
  it("should sync multiple packages", async ({ expect }) => {
129
- class TestVendorService extends VendorService {
130
- protected override async cloneRemote(): Promise<string> {
131
- return "/tmp/test-clone";
132
- }
133
- }
134
-
135
- const alepha = Alepha.create()
136
- .with({ provide: ShellProvider, use: MemoryShellProvider })
137
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
138
-
139
- const testService = alepha.inject(TestVendorService);
140
- const testFs = alepha.inject(MemoryFileSystemProvider);
137
+ const { service, fs } = createTestService(
138
+ Alepha.create()
139
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
140
+ .inject(MemoryShellProvider),
141
+ );
141
142
 
142
- await testFs.mkdir("/tmp/test-clone/packages/pkg-a", { recursive: true });
143
- await testFs.writeFile("/tmp/test-clone/packages/pkg-a/index.ts", "a");
144
- await testFs.mkdir("/tmp/test-clone/packages/pkg-b", { recursive: true });
145
- await testFs.writeFile("/tmp/test-clone/packages/pkg-b/index.ts", "b");
143
+ await fs.mkdir("/tmp/test-clone/packages/pkg-a", { recursive: true });
144
+ await fs.writeFile("/tmp/test-clone/packages/pkg-a/index.ts", "a");
145
+ await fs.mkdir("/tmp/test-clone/packages/pkg-b", { recursive: true });
146
+ await fs.writeFile("/tmp/test-clone/packages/pkg-b/index.ts", "b");
146
147
 
147
- const result = await testService.sync({
148
+ const result = await service.sync({
148
149
  root: "/project",
149
150
  remote: "remote",
150
151
  branch: "main",
@@ -161,6 +162,10 @@ describe("VendorService", () => {
161
162
  protected override async cloneRemote(): Promise<string> {
162
163
  return "/tmp/test-clone";
163
164
  }
165
+
166
+ protected override async getCommitHash(): Promise<string> {
167
+ return "abc123";
168
+ }
164
169
  }
165
170
 
166
171
  const alepha = Alepha.create()
@@ -195,102 +200,171 @@ describe("VendorService", () => {
195
200
 
196
201
  testFs.cp = originalCp;
197
202
  });
198
- });
199
203
 
200
- describe("diff", () => {
201
- it("should clone the remote repository for diff", async ({ expect }) => {
202
- const { service, shell } = createTestEnv();
204
+ it("should write vendor.json after successful sync", async ({ expect }) => {
205
+ const { service, fs } = createTestService(
206
+ Alepha.create()
207
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
208
+ .inject(MemoryShellProvider),
209
+ );
203
210
 
204
- await service.diff({
211
+ await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true });
212
+ await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "code");
213
+
214
+ await service.sync({
205
215
  root: "/project",
206
- remote: "git@github.com:user/repo.git",
216
+ remote: "remote",
207
217
  branch: "main",
208
- packages: [],
218
+ packages: ["my-pkg"],
209
219
  });
210
220
 
211
- expect(
212
- shell.wasCalledMatching(
213
- /git clone --depth 1 --branch main --filter=blob:none/,
214
- ),
215
- ).toBe(true);
221
+ expect(fs.wasWritten("/project/.alepha/vendor.json")).toBe(true);
222
+ const content = await fs.readFile("/project/.alepha/vendor.json");
223
+ const lock = JSON.parse(content.toString());
224
+ expect(lock.commit).toBe("abc123");
216
225
  });
217
226
 
218
- it("should detect added files", async ({ expect }) => {
219
- class TestVendorService extends VendorService {
220
- protected override async cloneRemote(): Promise<string> {
221
- return "/tmp/test-clone";
222
- }
223
- }
227
+ it("should skip modification check on first sync (no vendor.json)", async ({
228
+ expect,
229
+ }) => {
230
+ const { service, fs } = createTestService(
231
+ Alepha.create()
232
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
233
+ .inject(MemoryShellProvider),
234
+ );
224
235
 
225
- const alepha = Alepha.create()
226
- .with({ provide: ShellProvider, use: MemoryShellProvider })
227
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
236
+ await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true });
237
+ await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "code");
228
238
 
229
- const testService = alepha.inject(TestVendorService);
230
- const testFs = alepha.inject(MemoryFileSystemProvider);
239
+ const result = await service.sync({
240
+ root: "/project",
241
+ remote: "remote",
242
+ branch: "main",
243
+ packages: ["my-pkg"],
244
+ });
245
+
246
+ expect(result.synced).toEqual(["my-pkg"]);
247
+ expect(result.aborted).toBeUndefined();
248
+ });
231
249
 
232
- await testFs.mkdir("/tmp/test-clone/packages/pkg", { recursive: true });
233
- await testFs.writeFile("/tmp/test-clone/packages/pkg/new-file.ts", "new");
234
- await testFs.writeFile("/tmp/test-clone/packages/pkg/shared.ts", "same");
250
+ it("should abort when local modifications detected against baseline", async ({
251
+ expect,
252
+ }) => {
253
+ const { service, fs } = createTestService(
254
+ Alepha.create()
255
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
256
+ .inject(MemoryShellProvider),
257
+ );
258
+
259
+ // Baseline (last synced state)
260
+ await fs.mkdir("/tmp/test-baseline/packages/my-pkg", { recursive: true });
261
+ await fs.writeFile(
262
+ "/tmp/test-baseline/packages/my-pkg/index.ts",
263
+ "original",
264
+ );
235
265
 
236
- await testFs.mkdir("/project/packages/pkg", { recursive: true });
237
- await testFs.writeFile("/project/packages/pkg/shared.ts", "same");
266
+ // Local (user modified)
267
+ await fs.mkdir("/project/packages/my-pkg", { recursive: true });
268
+ await fs.writeFile("/project/packages/my-pkg/index.ts", "modified");
238
269
 
239
- const result = await testService.diff({
270
+ // Remote (latest)
271
+ await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true });
272
+ await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "latest");
273
+
274
+ // Vendor lock exists from a previous sync
275
+ await fs.mkdir("/project/.alepha", { recursive: true });
276
+ await fs.writeFile(
277
+ "/project/.alepha/vendor.json",
278
+ JSON.stringify({ commit: "old-hash" }),
279
+ );
280
+
281
+ const result = await service.sync({
240
282
  root: "/project",
241
283
  remote: "remote",
242
284
  branch: "main",
243
- packages: ["pkg"],
285
+ packages: ["my-pkg"],
244
286
  });
245
287
 
246
- expect(result.packages).toHaveLength(1);
247
- expect(result.packages[0].added).toEqual(["new-file.ts"]);
248
- expect(result.packages[0].modified).toEqual([]);
249
- expect(result.packages[0].removed).toEqual([]);
250
- expect(result.totalChanges).toBe(1);
288
+ expect(result.aborted).toBeDefined();
289
+ expect(result.synced).toEqual([]);
251
290
  });
252
291
 
253
- it("should detect modified files", async ({ expect }) => {
254
- class TestVendorService extends VendorService {
255
- protected override async cloneRemote(): Promise<string> {
256
- return "/tmp/test-clone";
257
- }
258
- }
292
+ it("should sync when local matches baseline", async ({ expect }) => {
293
+ const { service, fs } = createTestService(
294
+ Alepha.create()
295
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
296
+ .inject(MemoryShellProvider),
297
+ );
259
298
 
260
- const alepha = Alepha.create()
261
- .with({ provide: ShellProvider, use: MemoryShellProvider })
262
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
299
+ // Baseline and local are identical
300
+ await fs.mkdir("/tmp/test-baseline/packages/my-pkg", { recursive: true });
301
+ await fs.writeFile("/tmp/test-baseline/packages/my-pkg/index.ts", "same");
263
302
 
264
- const testService = alepha.inject(TestVendorService);
265
- const testFs = alepha.inject(MemoryFileSystemProvider);
303
+ await fs.mkdir("/project/packages/my-pkg", { recursive: true });
304
+ await fs.writeFile("/project/packages/my-pkg/index.ts", "same");
305
+
306
+ // Remote has updates
307
+ await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true });
308
+ await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "updated");
266
309
 
267
- await testFs.mkdir("/tmp/test-clone/packages/pkg", { recursive: true });
268
- await testFs.writeFile(
269
- "/tmp/test-clone/packages/pkg/file.ts",
270
- "updated content",
310
+ await fs.mkdir("/project/.alepha", { recursive: true });
311
+ await fs.writeFile(
312
+ "/project/.alepha/vendor.json",
313
+ JSON.stringify({ commit: "old-hash" }),
314
+ );
315
+
316
+ const result = await service.sync({
317
+ root: "/project",
318
+ remote: "remote",
319
+ branch: "main",
320
+ packages: ["my-pkg"],
321
+ });
322
+
323
+ expect(result.synced).toEqual(["my-pkg"]);
324
+ expect(result.aborted).toBeUndefined();
325
+ });
326
+
327
+ it("should skip modification check with --force even when vendor.json exists", async ({
328
+ expect,
329
+ }) => {
330
+ const { service, fs } = createTestService(
331
+ Alepha.create()
332
+ .with({ provide: ShellProvider, use: MemoryShellProvider })
333
+ .inject(MemoryShellProvider),
271
334
  );
272
335
 
273
- await testFs.mkdir("/project/packages/pkg", { recursive: true });
274
- await testFs.writeFile(
275
- "/project/packages/pkg/file.ts",
276
- "original content",
336
+ // Local has modifications
337
+ await fs.mkdir("/project/packages/my-pkg", { recursive: true });
338
+ await fs.writeFile("/project/packages/my-pkg/index.ts", "modified");
339
+
340
+ // Remote
341
+ await fs.mkdir("/tmp/test-clone/packages/my-pkg", { recursive: true });
342
+ await fs.writeFile("/tmp/test-clone/packages/my-pkg/index.ts", "latest");
343
+
344
+ await fs.mkdir("/project/.alepha", { recursive: true });
345
+ await fs.writeFile(
346
+ "/project/.alepha/vendor.json",
347
+ JSON.stringify({ commit: "old-hash" }),
277
348
  );
278
349
 
279
- const result = await testService.diff({
350
+ const result = await service.sync({
280
351
  root: "/project",
281
352
  remote: "remote",
282
353
  branch: "main",
283
- packages: ["pkg"],
354
+ packages: ["my-pkg"],
355
+ force: true,
284
356
  });
285
357
 
286
- expect(result.packages[0].modified).toEqual(["file.ts"]);
287
- expect(result.totalChanges).toBe(1);
358
+ expect(result.synced).toEqual(["my-pkg"]);
359
+ expect(result.aborted).toBeUndefined();
288
360
  });
361
+ });
289
362
 
290
- it("should detect removed files", async ({ expect }) => {
363
+ describe("diff", () => {
364
+ const createDiffTestService = () => {
291
365
  class TestVendorService extends VendorService {
292
- protected override async cloneRemote(): Promise<string> {
293
- return "/tmp/test-clone";
366
+ protected override async cloneAtCommit(): Promise<string> {
367
+ return "/tmp/test-baseline";
294
368
  }
295
369
  }
296
370
 
@@ -298,110 +372,141 @@ describe("VendorService", () => {
298
372
  .with({ provide: ShellProvider, use: MemoryShellProvider })
299
373
  .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
300
374
 
301
- const testService = alepha.inject(TestVendorService);
302
- const testFs = alepha.inject(MemoryFileSystemProvider);
375
+ return {
376
+ service: alepha.inject(TestVendorService),
377
+ fs: alepha.inject(MemoryFileSystemProvider),
378
+ };
379
+ };
303
380
 
304
- await testFs.mkdir("/tmp/test-clone/packages/pkg", { recursive: true });
305
- await testFs.writeFile("/tmp/test-clone/packages/pkg/shared.ts", "same");
381
+ const writeVendorLock = async (fs: MemoryFileSystemProvider) => {
382
+ await fs.mkdir("/project/.alepha", { recursive: true });
383
+ await fs.writeFile(
384
+ "/project/.alepha/vendor.json",
385
+ JSON.stringify({ commit: "abc123" }),
386
+ );
387
+ };
306
388
 
307
- await testFs.mkdir("/project/packages/pkg", { recursive: true });
308
- await testFs.writeFile("/project/packages/pkg/shared.ts", "same");
309
- await testFs.writeFile("/project/packages/pkg/local-only.ts", "local");
389
+ it("should return no changes when no vendor.json exists", async ({
390
+ expect,
391
+ }) => {
392
+ const { service } = createDiffTestService();
310
393
 
311
- const result = await testService.diff({
394
+ const result = await service.diff({
312
395
  root: "/project",
313
396
  remote: "remote",
314
397
  branch: "main",
315
398
  packages: ["pkg"],
316
399
  });
317
400
 
318
- expect(result.packages[0].removed).toEqual(["local-only.ts"]);
319
- expect(result.totalChanges).toBe(1);
401
+ expect(result.packages).toEqual([]);
402
+ expect(result.totalChanges).toBe(0);
320
403
  });
321
404
 
322
- it("should handle package only in local", async ({ expect }) => {
323
- class TestVendorService extends VendorService {
324
- protected override async cloneRemote(): Promise<string> {
325
- return "/tmp/test-clone";
326
- }
327
- }
405
+ it("should detect locally added files", async ({ expect }) => {
406
+ const { service, fs } = createDiffTestService();
407
+ await writeVendorLock(fs);
328
408
 
329
- const alepha = Alepha.create()
330
- .with({ provide: ShellProvider, use: MemoryShellProvider })
331
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
409
+ // Baseline (last synced state)
410
+ await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true });
411
+ await fs.writeFile("/tmp/test-baseline/packages/pkg/shared.ts", "same");
332
412
 
333
- const testService = alepha.inject(TestVendorService);
334
- const testFs = alepha.inject(MemoryFileSystemProvider);
335
-
336
- await testFs.mkdir("/tmp/test-clone/packages", { recursive: true });
337
- await testFs.mkdir("/project/packages/pkg", { recursive: true });
338
- await testFs.writeFile("/project/packages/pkg/file.ts", "local");
413
+ // Local (user added a file)
414
+ await fs.mkdir("/project/packages/pkg", { recursive: true });
415
+ await fs.writeFile("/project/packages/pkg/shared.ts", "same");
416
+ await fs.writeFile("/project/packages/pkg/local-only.ts", "local");
339
417
 
340
- const result = await testService.diff({
418
+ const result = await service.diff({
341
419
  root: "/project",
342
420
  remote: "remote",
343
421
  branch: "main",
344
422
  packages: ["pkg"],
345
423
  });
346
424
 
347
- expect(result.packages[0].removed).toEqual(["file.ts"]);
348
- expect(result.packages[0].added).toEqual([]);
425
+ expect(result.packages[0].added).toEqual(["local-only.ts"]);
349
426
  expect(result.totalChanges).toBe(1);
350
427
  });
351
428
 
352
- it("should handle package only in remote", async ({ expect }) => {
353
- class TestVendorService extends VendorService {
354
- protected override async cloneRemote(): Promise<string> {
355
- return "/tmp/test-clone";
356
- }
357
- }
429
+ it("should detect locally modified files", async ({ expect }) => {
430
+ const { service, fs } = createDiffTestService();
431
+ await writeVendorLock(fs);
358
432
 
359
- const alepha = Alepha.create()
360
- .with({ provide: ShellProvider, use: MemoryShellProvider })
361
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
433
+ await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true });
434
+ await fs.writeFile("/tmp/test-baseline/packages/pkg/file.ts", "original");
362
435
 
363
- const testService = alepha.inject(TestVendorService);
364
- const testFs = alepha.inject(MemoryFileSystemProvider);
436
+ await fs.mkdir("/project/packages/pkg", { recursive: true });
437
+ await fs.writeFile("/project/packages/pkg/file.ts", "modified by user");
438
+
439
+ const result = await service.diff({
440
+ root: "/project",
441
+ remote: "remote",
442
+ branch: "main",
443
+ packages: ["pkg"],
444
+ });
445
+
446
+ expect(result.packages[0].modified).toEqual(["file.ts"]);
447
+ expect(result.totalChanges).toBe(1);
448
+ });
449
+
450
+ it("should detect locally removed files", async ({ expect }) => {
451
+ const { service, fs } = createDiffTestService();
452
+ await writeVendorLock(fs);
365
453
 
366
- await testFs.mkdir("/tmp/test-clone/packages/pkg", { recursive: true });
367
- await testFs.writeFile("/tmp/test-clone/packages/pkg/file.ts", "remote");
454
+ // Baseline had two files
455
+ await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true });
456
+ await fs.writeFile("/tmp/test-baseline/packages/pkg/kept.ts", "same");
457
+ await fs.writeFile("/tmp/test-baseline/packages/pkg/deleted.ts", "gone");
368
458
 
369
- const result = await testService.diff({
459
+ // Local only has one
460
+ await fs.mkdir("/project/packages/pkg", { recursive: true });
461
+ await fs.writeFile("/project/packages/pkg/kept.ts", "same");
462
+
463
+ const result = await service.diff({
370
464
  root: "/project",
371
465
  remote: "remote",
372
466
  branch: "main",
373
467
  packages: ["pkg"],
374
468
  });
375
469
 
376
- expect(result.packages[0].added).toEqual(["file.ts"]);
377
- expect(result.packages[0].removed).toEqual([]);
470
+ expect(result.packages[0].removed).toEqual(["deleted.ts"]);
378
471
  expect(result.totalChanges).toBe(1);
379
472
  });
380
473
 
381
- it("should clean up temp directory after diff", async ({ expect }) => {
382
- class TestVendorService extends VendorService {
383
- protected override async cloneRemote(): Promise<string> {
384
- return "/tmp/test-clone";
385
- }
386
- }
474
+ it("should report no changes when local matches baseline", async ({
475
+ expect,
476
+ }) => {
477
+ const { service, fs } = createDiffTestService();
478
+ await writeVendorLock(fs);
387
479
 
388
- const alepha = Alepha.create()
389
- .with({ provide: ShellProvider, use: MemoryShellProvider })
390
- .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider });
480
+ await fs.mkdir("/tmp/test-baseline/packages/pkg", { recursive: true });
481
+ await fs.writeFile("/tmp/test-baseline/packages/pkg/file.ts", "same");
391
482
 
392
- const testService = alepha.inject(TestVendorService);
393
- const testFs = alepha.inject(MemoryFileSystemProvider);
483
+ await fs.mkdir("/project/packages/pkg", { recursive: true });
484
+ await fs.writeFile("/project/packages/pkg/file.ts", "same");
394
485
 
395
- await testFs.mkdir("/tmp/test-clone/packages", { recursive: true });
486
+ const result = await service.diff({
487
+ root: "/project",
488
+ remote: "remote",
489
+ branch: "main",
490
+ packages: ["pkg"],
491
+ });
396
492
 
397
- await testService.diff({
493
+ expect(result.totalChanges).toBe(0);
494
+ });
495
+
496
+ it("should clean up temp directory after diff", async ({ expect }) => {
497
+ const { service, fs } = createDiffTestService();
498
+ await writeVendorLock(fs);
499
+
500
+ await fs.mkdir("/tmp/test-baseline/packages", { recursive: true });
501
+
502
+ await service.diff({
398
503
  root: "/project",
399
504
  remote: "remote",
400
505
  branch: "main",
401
506
  packages: [],
402
507
  });
403
508
 
404
- expect(testFs.wasDeleted("/tmp/test-clone")).toBe(true);
509
+ expect(fs.wasDeleted("/tmp/test-baseline")).toBe(true);
405
510
  });
406
511
  });
407
512
  });
@@ -1,10 +1,19 @@
1
1
  import { $module } from "alepha";
2
- import { vendorOptions } from "./atoms/vendorOptions.ts";
2
+ import { cliConfigPlugins } from "alepha/cli/config";
3
+ import { type VendorOptions, vendorOptions } from "./atoms/vendorOptions.ts";
3
4
  import { VendorCommand } from "./commands/VendorCommand.ts";
4
5
  import { VendorService } from "./services/VendorService.ts";
5
6
 
6
7
  // ---------------------------------------------------------------------------
7
8
 
9
+ declare module "alepha/cli/config" {
10
+ interface AlephaCliConfig {
11
+ vendor?: VendorOptions;
12
+ }
13
+ }
14
+
15
+ // ---------------------------------------------------------------------------
16
+
8
17
  /**
9
18
  * CLI plugin for vendoring Alepha packages into external projects.
10
19
  *
@@ -19,10 +28,10 @@ import { VendorService } from "./services/VendorService.ts";
19
28
  * Configuration in `alepha.config.ts`:
20
29
  *
21
30
  * ```typescript
22
- * import { AlephaCliVendor } from "alepha/cli/vendor";
31
+ * import { AlephaCliVendorPlugin } from "alepha/cli/vendor";
23
32
  *
24
33
  * export default defineConfig({
25
- * services: [AlephaCliVendor],
34
+ * services: [AlephaCliVendorPlugin],
26
35
  * vendor: {
27
36
  * branch: "main",
28
37
  * packages: ["alepha", "@alepha/bucket-s3"],
@@ -38,6 +47,14 @@ export const AlephaCliVendorPlugin = $module({
38
47
 
39
48
  // ---------------------------------------------------------------------------
40
49
 
50
+ cliConfigPlugins.push((config, alepha) => {
51
+ if (config.vendor) {
52
+ alepha.set(vendorOptions, config.vendor);
53
+ }
54
+ });
55
+
56
+ // ---------------------------------------------------------------------------
57
+
41
58
  export * from "./atoms/vendorOptions.ts";
42
59
  export * from "./commands/VendorCommand.ts";
43
60
  export * from "./services/VendorService.ts";