alpic 0.0.0-dev.f19a8ca → 0.0.0-dev.f1adf05

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 (203) hide show
  1. package/README.md +40 -0
  2. package/bin/run.js +5 -0
  3. package/dist/__tests__/audit.e2e.test.d.ts +1 -0
  4. package/dist/__tests__/audit.e2e.test.js +562 -0
  5. package/dist/__tests__/audit.e2e.test.js.map +1 -0
  6. package/dist/__tests__/auth.e2e.test.d.ts +1 -0
  7. package/dist/__tests__/auth.e2e.test.js +162 -0
  8. package/dist/__tests__/auth.e2e.test.js.map +1 -0
  9. package/dist/__tests__/deploy-flags.e2e.test.d.ts +1 -0
  10. package/dist/__tests__/deploy-flags.e2e.test.js +228 -0
  11. package/dist/__tests__/deploy-flags.e2e.test.js.map +1 -0
  12. package/dist/__tests__/deploy.e2e.test.js +66 -66
  13. package/dist/__tests__/deploy.e2e.test.js.map +1 -1
  14. package/dist/__tests__/deployment-inspect.e2e.test.d.ts +1 -0
  15. package/dist/__tests__/deployment-inspect.e2e.test.js +119 -0
  16. package/dist/__tests__/deployment-inspect.e2e.test.js.map +1 -0
  17. package/dist/__tests__/deployment-list.e2e.test.d.ts +1 -0
  18. package/dist/__tests__/deployment-list.e2e.test.js +121 -0
  19. package/dist/__tests__/deployment-list.e2e.test.js.map +1 -0
  20. package/dist/__tests__/deployment-logs.e2e.test.d.ts +1 -0
  21. package/dist/__tests__/deployment-logs.e2e.test.js +260 -0
  22. package/dist/__tests__/deployment-logs.e2e.test.js.map +1 -0
  23. package/dist/__tests__/environment-variable/environment-variable-add.e2e.test.d.ts +1 -0
  24. package/dist/__tests__/environment-variable/environment-variable-add.e2e.test.js +264 -0
  25. package/dist/__tests__/environment-variable/environment-variable-add.e2e.test.js.map +1 -0
  26. package/dist/__tests__/environment-variable/environment-variable-list.e2e.test.d.ts +1 -0
  27. package/dist/__tests__/environment-variable/environment-variable-list.e2e.test.js +144 -0
  28. package/dist/__tests__/environment-variable/environment-variable-list.e2e.test.js.map +1 -0
  29. package/dist/__tests__/environment-variable/environment-variable-remove.e2e.test.d.ts +1 -0
  30. package/dist/__tests__/environment-variable/environment-variable-remove.e2e.test.js +155 -0
  31. package/dist/__tests__/environment-variable/environment-variable-remove.e2e.test.js.map +1 -0
  32. package/dist/__tests__/environment-variable/environment-variable-update.e2e.test.d.ts +1 -0
  33. package/dist/__tests__/environment-variable/environment-variable-update.e2e.test.js +347 -0
  34. package/dist/__tests__/environment-variable/environment-variable-update.e2e.test.js.map +1 -0
  35. package/dist/__tests__/environment-variable/environment-variable-validation.test.d.ts +1 -0
  36. package/dist/__tests__/environment-variable/environment-variable-validation.test.js +20 -0
  37. package/dist/__tests__/environment-variable/environment-variable-validation.test.js.map +1 -0
  38. package/dist/__tests__/fixtures/demo-project/index.js +1 -1
  39. package/dist/__tests__/fixtures/demo-project/index.js.map +1 -1
  40. package/dist/__tests__/git-flags.e2e.test.d.ts +1 -0
  41. package/dist/__tests__/git-flags.e2e.test.js +129 -0
  42. package/dist/__tests__/git-flags.e2e.test.js.map +1 -0
  43. package/dist/__tests__/git.e2e.test.js +67 -21
  44. package/dist/__tests__/git.e2e.test.js.map +1 -1
  45. package/dist/__tests__/link.e2e.test.d.ts +1 -0
  46. package/dist/__tests__/link.e2e.test.js +284 -0
  47. package/dist/__tests__/link.e2e.test.js.map +1 -0
  48. package/dist/__tests__/logs.e2e.test.d.ts +1 -0
  49. package/dist/__tests__/logs.e2e.test.js +231 -0
  50. package/dist/__tests__/logs.e2e.test.js.map +1 -0
  51. package/dist/__tests__/mock-server.d.ts +23 -0
  52. package/dist/__tests__/mock-server.js +377 -21
  53. package/dist/__tests__/mock-server.js.map +1 -1
  54. package/dist/__tests__/publish.e2e.test.d.ts +1 -0
  55. package/dist/__tests__/publish.e2e.test.js +510 -0
  56. package/dist/__tests__/publish.e2e.test.js.map +1 -0
  57. package/dist/__tests__/tunnel.e2e.test.d.ts +1 -0
  58. package/dist/__tests__/tunnel.e2e.test.js +66 -0
  59. package/dist/__tests__/tunnel.e2e.test.js.map +1 -0
  60. package/dist/__tests__/utils.d.ts +23 -5
  61. package/dist/__tests__/utils.js +138 -21
  62. package/dist/__tests__/utils.js.map +1 -1
  63. package/dist/api.d.ts +1 -2
  64. package/dist/api.js +13 -10
  65. package/dist/api.js.map +1 -1
  66. package/dist/commands/audit.d.ts +14 -0
  67. package/dist/commands/audit.js +49 -0
  68. package/dist/commands/audit.js.map +1 -0
  69. package/dist/commands/deploy.d.ts +8 -2
  70. package/dist/commands/deploy.js +64 -29
  71. package/dist/commands/deploy.js.map +1 -1
  72. package/dist/commands/deployment/inspect.d.ts +11 -0
  73. package/dist/commands/deployment/inspect.js +91 -0
  74. package/dist/commands/deployment/inspect.js.map +1 -0
  75. package/dist/commands/deployment/list.d.ts +11 -0
  76. package/dist/commands/deployment/list.js +97 -0
  77. package/dist/commands/deployment/list.js.map +1 -0
  78. package/dist/commands/deployment/logs.d.ts +12 -0
  79. package/dist/commands/deployment/logs.js +50 -0
  80. package/dist/commands/deployment/logs.js.map +1 -0
  81. package/dist/commands/environment-variable/add.d.ts +14 -0
  82. package/dist/commands/environment-variable/add.js +47 -0
  83. package/dist/commands/environment-variable/add.js.map +1 -0
  84. package/dist/commands/environment-variable/list.d.ts +9 -0
  85. package/dist/commands/environment-variable/list.js +45 -0
  86. package/dist/commands/environment-variable/list.js.map +1 -0
  87. package/dist/commands/environment-variable/remove.d.ts +11 -0
  88. package/dist/commands/environment-variable/remove.js +33 -0
  89. package/dist/commands/environment-variable/remove.js.map +1 -0
  90. package/dist/commands/environment-variable/update.d.ts +13 -0
  91. package/dist/commands/environment-variable/update.js +41 -0
  92. package/dist/commands/environment-variable/update.js.map +1 -0
  93. package/dist/commands/git/connect.d.ts +3 -2
  94. package/dist/commands/git/connect.js +29 -34
  95. package/dist/commands/git/connect.js.map +1 -1
  96. package/dist/commands/git/disconnect.d.ts +2 -2
  97. package/dist/commands/git/disconnect.js +20 -30
  98. package/dist/commands/git/disconnect.js.map +1 -1
  99. package/dist/commands/git.js +3 -3
  100. package/dist/commands/git.js.map +1 -1
  101. package/dist/commands/link.d.ts +17 -0
  102. package/dist/commands/link.js +101 -0
  103. package/dist/commands/link.js.map +1 -0
  104. package/dist/commands/login.d.ts +6 -0
  105. package/dist/commands/login.js +21 -0
  106. package/dist/commands/login.js.map +1 -0
  107. package/dist/commands/logout.d.ts +6 -0
  108. package/dist/commands/logout.js +20 -0
  109. package/dist/commands/logout.js.map +1 -0
  110. package/dist/commands/logs.d.ts +16 -0
  111. package/dist/commands/logs.js +96 -0
  112. package/dist/commands/logs.js.map +1 -0
  113. package/dist/commands/publish.d.ts +15 -0
  114. package/dist/commands/publish.js +51 -0
  115. package/dist/commands/publish.js.map +1 -0
  116. package/dist/commands/tunnel.d.ts +10 -0
  117. package/dist/commands/tunnel.js +132 -0
  118. package/dist/commands/tunnel.js.map +1 -0
  119. package/dist/commands/whoami.d.ts +6 -0
  120. package/dist/commands/whoami.js +13 -0
  121. package/dist/commands/whoami.js.map +1 -0
  122. package/dist/env.d.ts +4 -0
  123. package/dist/env.js +10 -0
  124. package/dist/env.js.map +1 -0
  125. package/dist/lib/alpic-command.d.ts +2 -0
  126. package/dist/lib/alpic-command.js +12 -7
  127. package/dist/lib/alpic-command.js.map +1 -1
  128. package/dist/lib/archive.d.ts +3 -3
  129. package/dist/lib/archive.js +21 -19
  130. package/dist/lib/archive.js.map +1 -1
  131. package/dist/lib/audit.d.ts +16 -0
  132. package/dist/lib/audit.js +245 -0
  133. package/dist/lib/audit.js.map +1 -0
  134. package/dist/lib/auth/auth.d.ts +5 -0
  135. package/dist/lib/auth/auth.js +44 -0
  136. package/dist/lib/auth/auth.js.map +1 -0
  137. package/dist/lib/auth/oauth/client.d.ts +28 -0
  138. package/dist/lib/auth/oauth/client.js +110 -0
  139. package/dist/lib/auth/oauth/client.js.map +1 -0
  140. package/dist/lib/auth/oauth/constants.d.ts +2 -0
  141. package/dist/lib/auth/oauth/constants.js +3 -0
  142. package/dist/lib/auth/oauth/constants.js.map +1 -0
  143. package/dist/lib/auth/oauth/server/assets/alpic-mountain.png +0 -0
  144. package/dist/lib/auth/oauth/server/assets/authorize.html +195 -0
  145. package/dist/lib/auth/oauth/server/assets/callback.html +88 -0
  146. package/dist/lib/auth/oauth/server/index.d.ts +9 -0
  147. package/dist/lib/auth/oauth/server/index.js +128 -0
  148. package/dist/lib/auth/oauth/server/index.js.map +1 -0
  149. package/dist/lib/auth/whoami.d.ts +1 -0
  150. package/dist/lib/auth/whoami.js +45 -0
  151. package/dist/lib/auth/whoami.js.map +1 -0
  152. package/dist/lib/base-workflow.d.ts +10 -0
  153. package/dist/lib/base-workflow.js +22 -0
  154. package/dist/lib/base-workflow.js.map +1 -0
  155. package/dist/lib/config.d.ts +3 -2
  156. package/dist/lib/config.js +8 -7
  157. package/dist/lib/config.js.map +1 -1
  158. package/dist/lib/deployment.d.ts +71 -4
  159. package/dist/lib/deployment.js +122 -9
  160. package/dist/lib/deployment.js.map +1 -1
  161. package/dist/lib/environment-variable.d.ts +37 -0
  162. package/dist/lib/environment-variable.js +299 -0
  163. package/dist/lib/environment-variable.js.map +1 -0
  164. package/dist/lib/git.d.ts +12 -4
  165. package/dist/lib/git.js +52 -27
  166. package/dist/lib/git.js.map +1 -1
  167. package/dist/lib/global-store.d.ts +28 -0
  168. package/dist/lib/global-store.js +76 -0
  169. package/dist/lib/global-store.js.map +1 -0
  170. package/dist/lib/link.d.ts +83 -0
  171. package/dist/lib/link.js +387 -0
  172. package/dist/lib/link.js.map +1 -0
  173. package/dist/lib/logs.d.ts +20 -0
  174. package/dist/lib/logs.js +87 -0
  175. package/dist/lib/logs.js.map +1 -0
  176. package/dist/lib/publish.d.ts +22 -0
  177. package/dist/lib/publish.js +185 -0
  178. package/dist/lib/publish.js.map +1 -0
  179. package/dist/lib/resolve.d.ts +6 -0
  180. package/dist/lib/resolve.js +26 -0
  181. package/dist/lib/resolve.js.map +1 -0
  182. package/dist/lib/table.d.ts +8 -0
  183. package/dist/lib/table.js +27 -0
  184. package/dist/lib/table.js.map +1 -0
  185. package/dist/lib/telemetry.js +7 -7
  186. package/dist/lib/telemetry.js.map +1 -1
  187. package/dist/lib/utils.d.ts +5 -0
  188. package/dist/lib/utils.js +53 -0
  189. package/dist/lib/utils.js.map +1 -0
  190. package/dist/lib/utils.test.d.ts +1 -0
  191. package/dist/lib/utils.test.js +27 -0
  192. package/dist/lib/utils.test.js.map +1 -0
  193. package/dist/types.d.ts +1 -0
  194. package/package.json +35 -21
  195. package/dist/lib/auth.d.ts +0 -1
  196. package/dist/lib/auth.js +0 -10
  197. package/dist/lib/auth.js.map +0 -1
  198. package/dist/lib/global-config.d.ts +0 -9
  199. package/dist/lib/global-config.js +0 -48
  200. package/dist/lib/global-config.js.map +0 -1
  201. package/dist/lib/project.d.ts +0 -67
  202. package/dist/lib/project.js +0 -285
  203. package/dist/lib/project.js.map +0 -1
@@ -1,8 +1,23 @@
1
- import { OpenAPIHandler } from "@orpc/openapi/node";
2
- import { ORPCError, implement } from "@orpc/server";
3
- import express, {} from "express";
4
1
  import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import * as https from "node:https";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
5
6
  import { contract } from "@alpic-ai/api";
7
+ import { OpenAPIHandler } from "@orpc/openapi/node";
8
+ import { implement, ORPCError } from "@orpc/server";
9
+ import express, {} from "express";
10
+ import { inc as semverInc } from "semver";
11
+ const __testDirname = dirname(fileURLToPath(import.meta.url));
12
+ const FIXTURE_CERT_PATH = join(__testDirname, "fixtures", "certs", "localhost-cert.pem");
13
+ const FIXTURE_KEY_PATH = join(__testDirname, "fixtures", "certs", "localhost-key.pem");
14
+ function getTlsAssets() {
15
+ return {
16
+ key: readFileSync(FIXTURE_KEY_PATH, "utf8"),
17
+ cert: readFileSync(FIXTURE_CERT_PATH, "utf8"),
18
+ certPath: FIXTURE_CERT_PATH,
19
+ };
20
+ }
6
21
  export class MockApiServer {
7
22
  server = null;
8
23
  app;
@@ -12,9 +27,18 @@ export class MockApiServer {
12
27
  constructor() {
13
28
  this.app = express();
14
29
  this.mockData = {
30
+ teams: null,
15
31
  projects: new Map(),
16
32
  environments: new Map(),
17
33
  deployments: new Map(),
34
+ environmentVariables: new Map(),
35
+ runtimeLogs: new Map(),
36
+ runtimeLogPages: new Map(),
37
+ deploymentLogPages: new Map(),
38
+ deploymentLogCallCounts: new Map(),
39
+ publishedServers: new Map(),
40
+ beaconGetResponses: [],
41
+ beaconGetCallCount: 0,
18
42
  };
19
43
  }
20
44
  async start() {
@@ -22,11 +46,17 @@ export class MockApiServer {
22
46
  throw new Error("Server is already running");
23
47
  }
24
48
  this.configureServer();
49
+ const tlsAssets = getTlsAssets();
50
+ process.env.NODE_EXTRA_CA_CERTS = tlsAssets.certPath;
51
+ const httpsOptions = { key: tlsAssets.key, cert: tlsAssets.cert };
25
52
  return new Promise((resolve) => {
26
- this.server = this.app.listen(0, "127.0.0.1", () => {
53
+ this.server = https.createServer(httpsOptions, this.app).listen(0, "127.0.0.1", () => {
54
+ if (!this.server) {
55
+ throw new Error("Server is not running");
56
+ }
27
57
  const address = this.server.address();
28
58
  this.port = address.port;
29
- const url = `http://127.0.0.1:${this.port}`;
59
+ const url = `https://127.0.0.1:${this.port}`;
30
60
  resolve(url);
31
61
  });
32
62
  });
@@ -44,17 +74,66 @@ export class MockApiServer {
44
74
  res.status(200).end();
45
75
  });
46
76
  this.app.use(express.json());
77
+ this.app.get("/.well-known/oauth-protected-resource", (_req, res) => {
78
+ const base = `https://127.0.0.1:${this.port}`;
79
+ res.status(200).json({
80
+ resource: "https://api.alpic.ai",
81
+ authorization_servers: [base],
82
+ bearer_methods_supported: ["header"],
83
+ scopes_supported: ["openid", "email", "profile"],
84
+ resource_documentation: "https://docs.alpic.ai",
85
+ });
86
+ });
87
+ this.app.get("/.well-known/openid-configuration", (_req, res) => {
88
+ const base = `https://127.0.0.1:${this.port}`;
89
+ res.status(200).json({
90
+ issuer: base,
91
+ userinfo_endpoint: `${base}/oauth2/userInfo`,
92
+ authorization_endpoint: `${base}/oauth2/authorize`,
93
+ token_endpoint: `${base}/oauth2/token`,
94
+ jwks_uri: `${base}/.well-known/jwks.json`,
95
+ });
96
+ });
97
+ this.app.get("/oauth2/userInfo", (req, res) => {
98
+ const auth = req.headers.authorization;
99
+ if (!auth?.startsWith("Bearer ")) {
100
+ res.status(401).json({ error: "Missing or invalid Authorization" });
101
+ return;
102
+ }
103
+ res.status(200).json({
104
+ sub: "test-sub",
105
+ email_verified: "true",
106
+ identities: [],
107
+ name: "Test User",
108
+ email: "test@example.com",
109
+ picture: "",
110
+ username: "testuser",
111
+ });
112
+ });
47
113
  const router = implement(contract).router({
114
+ tunnels: {
115
+ getTicket: {
116
+ v1: implement(contract.tunnels.getTicket.v1).handler(async () => {
117
+ return {
118
+ subdomain: "cool-mongoose-123",
119
+ ticket: "mock-ticket",
120
+ tunnelHost: "tunnel.alpic.dev",
121
+ };
122
+ }),
123
+ },
124
+ },
48
125
  teams: {
49
126
  list: {
50
127
  v1: implement(contract.teams.list.v1).handler(async () => {
128
+ if (this.mockData.teams) {
129
+ return this.mockData.teams;
130
+ }
51
131
  return [
52
132
  {
53
133
  id: "mock-team-id",
54
134
  name: "Mock Team",
55
135
  createdAt: new Date(),
56
136
  hasStripeAccount: false,
57
- hasActiveSubscription: false,
58
137
  },
59
138
  ];
60
139
  }),
@@ -65,7 +144,9 @@ export class MockApiServer {
65
144
  v1: implement(contract.analytics.get.v1).handler(async ({ input }) => {
66
145
  const project = this.mockData.projects.get(input.projectId);
67
146
  if (!project) {
68
- throw new ORPCError("NOT_FOUND", { message: "Project not found" });
147
+ throw new ORPCError("NOT_FOUND", {
148
+ message: "Project not found",
149
+ });
69
150
  }
70
151
  const emptyTimeSeries = {
71
152
  sessions_count: [],
@@ -114,7 +195,9 @@ export class MockApiServer {
114
195
  v1: implement(contract.projects.get.v1).handler(async ({ input }) => {
115
196
  const project = this.mockData.projects.get(input.projectId);
116
197
  if (!project) {
117
- throw new ORPCError("NOT_FOUND", { message: "Project not found" });
198
+ throw new ORPCError("NOT_FOUND", {
199
+ message: "Project not found",
200
+ });
118
201
  }
119
202
  return project;
120
203
  }),
@@ -122,7 +205,7 @@ export class MockApiServer {
122
205
  create: {
123
206
  v1: implement(contract.projects.create.v1).handler(async ({ input }) => {
124
207
  const projectId = randomUUID();
125
- const teamId = "mock-team-id";
208
+ const teamId = input.teamId ?? "mock-team-id";
126
209
  const productionEnvId = randomUUID();
127
210
  const productionEnvName = "production";
128
211
  const project = {
@@ -160,6 +243,7 @@ export class MockApiServer {
160
243
  id: productionEnvId,
161
244
  name: productionEnvName,
162
245
  mcpServerUrl: `https://mcp-${projectId}.alpic.ai`,
246
+ domains: [],
163
247
  latestDeployment: null,
164
248
  },
165
249
  environments: [
@@ -182,7 +266,9 @@ export class MockApiServer {
182
266
  v1: implement(contract.projects.update.v1).handler(async ({ input }) => {
183
267
  const project = this.mockData.projects.get(input.projectId);
184
268
  if (!project) {
185
- throw new ORPCError("NOT_FOUND", { message: "Project not found" });
269
+ throw new ORPCError("NOT_FOUND", {
270
+ message: "Project not found",
271
+ });
186
272
  }
187
273
  if (input.sourceRepository !== undefined) {
188
274
  project.sourceRepository = input.sourceRepository;
@@ -195,7 +281,9 @@ export class MockApiServer {
195
281
  v1: implement(contract.projects.delete.v1).handler(async ({ input }) => {
196
282
  const project = this.mockData.projects.get(input.projectId);
197
283
  if (!project) {
198
- throw new ORPCError("NOT_FOUND", { message: "Project not found" });
284
+ throw new ORPCError("NOT_FOUND", {
285
+ message: "Project not found",
286
+ });
199
287
  }
200
288
  this.mockData.projects.delete(input.projectId);
201
289
  return { success: true };
@@ -207,7 +295,9 @@ export class MockApiServer {
207
295
  v1: implement(contract.environments.create.v1).handler(async ({ input }) => {
208
296
  const project = this.mockData.projects.get(input.projectId);
209
297
  if (!project) {
210
- throw new ORPCError("NOT_FOUND", { message: "Project not found" });
298
+ throw new ORPCError("NOT_FOUND", {
299
+ message: "Project not found",
300
+ });
211
301
  }
212
302
  const environmentId = randomUUID();
213
303
  const mcpServerUrl = `https://mcp-${environmentId}.alpic.ai`;
@@ -251,21 +341,50 @@ export class MockApiServer {
251
341
  v1: implement(contract.environments.get.v1).handler(async ({ input }) => {
252
342
  const environment = this.mockData.environments.get(input.environmentId);
253
343
  if (!environment) {
254
- throw new ORPCError("NOT_FOUND", { message: "Environment not found" });
344
+ throw new ORPCError("NOT_FOUND", {
345
+ message: "Environment not found",
346
+ });
255
347
  }
256
348
  return environment;
257
349
  }),
258
350
  },
351
+ getLogs: {
352
+ v1: implement(contract.environments.getLogs.v1).handler(async ({ input }) => {
353
+ const environment = this.mockData.environments.get(input.environmentId);
354
+ if (!environment) {
355
+ throw new ORPCError("NOT_FOUND", {
356
+ message: "Environment not found",
357
+ });
358
+ }
359
+ const pagedLogs = this.mockData.runtimeLogPages.get(input.environmentId);
360
+ const pageIndex = input.nextToken === undefined ? 0 : Number.parseInt(input.nextToken, 10);
361
+ const allLogs = pagedLogs === undefined
362
+ ? (this.mockData.runtimeLogs.get(input.environmentId) ?? [])
363
+ : (pagedLogs[pageIndex] ?? []);
364
+ let logs = allLogs;
365
+ if (input.level !== undefined) {
366
+ const levels = input.level;
367
+ logs = allLogs.filter((log) => levels.includes(log.type));
368
+ }
369
+ const nextToken = pagedLogs === undefined || pageIndex + 1 >= pagedLogs.length ? null : String(pageIndex + 1);
370
+ return { logs, nextToken };
371
+ }),
372
+ },
259
373
  deploy: {
260
374
  v1: implement(contract.environments.deploy.v1).handler(async ({ input }) => {
261
375
  const environment = this.mockData.environments.get(input.environmentId);
262
376
  if (!environment) {
263
- throw new ORPCError("NOT_FOUND", { message: "Environment not found" });
377
+ throw new ORPCError("NOT_FOUND", {
378
+ message: "Environment not found",
379
+ });
264
380
  }
265
381
  const deploymentId = randomUUID();
266
382
  const deployment = {
267
383
  id: deploymentId,
268
384
  status: "ongoing",
385
+ environmentId: environment.id,
386
+ environmentName: environment.name,
387
+ isCurrent: false,
269
388
  sourceRef: environment.sourceBranch,
270
389
  sourceCommitId: randomUUID().slice(0, 7),
271
390
  sourceCommitMessage: "Mock deployment",
@@ -273,23 +392,187 @@ export class MockApiServer {
273
392
  authorAvatarUrl: null,
274
393
  startedAt: new Date(),
275
394
  completedAt: null,
395
+ deploymentPageUrl: "https://app.alpic.ai/deployments/mock",
276
396
  };
277
397
  this.mockData.deployments.set(deploymentId, deployment);
278
398
  return deployment;
279
399
  }),
280
400
  },
281
401
  },
402
+ environmentVariables: {
403
+ list: {
404
+ v1: implement(contract.environmentVariables.list.v1).handler(async ({ input }) => {
405
+ const environment = this.mockData.environments.get(input.environmentId);
406
+ if (!environment) {
407
+ throw new ORPCError("NOT_FOUND", {
408
+ message: "Environment not found",
409
+ });
410
+ }
411
+ return this.mockData.environmentVariables.get(input.environmentId) ?? [];
412
+ }),
413
+ },
414
+ create: {
415
+ v1: implement(contract.environmentVariables.create.v1).handler(async ({ input }) => {
416
+ const environment = this.mockData.environments.get(input.environmentId);
417
+ if (!environment) {
418
+ throw new ORPCError("NOT_FOUND", {
419
+ message: "Environment not found",
420
+ });
421
+ }
422
+ const existing = this.mockData.environmentVariables.get(input.environmentId) ?? [];
423
+ const newVars = input.environmentVariables.map((variable) => ({
424
+ id: randomUUID(),
425
+ key: variable.key,
426
+ value: variable.value,
427
+ isSecret: variable.isSecret,
428
+ createdAt: new Date(),
429
+ }));
430
+ for (const newVar of newVars) {
431
+ if (existing.some((existingVar) => existingVar.key === newVar.key)) {
432
+ throw new ORPCError("BAD_REQUEST", {
433
+ message: `Environment variable "${newVar.key}" already exists`,
434
+ });
435
+ }
436
+ }
437
+ this.mockData.environmentVariables.set(input.environmentId, [...existing, ...newVars]);
438
+ return { success: true };
439
+ }),
440
+ },
441
+ update: {
442
+ v1: implement(contract.environmentVariables.update.v1).handler(async ({ input }) => {
443
+ for (const [environmentId, variables] of this.mockData.environmentVariables.entries()) {
444
+ const index = variables.findIndex((variable) => variable.id === input.environmentVariableId);
445
+ if (index !== -1) {
446
+ const updated = variables.map((variable, i) => i === index
447
+ ? {
448
+ ...variable,
449
+ key: input.key,
450
+ value: input.value ?? variable.value,
451
+ isSecret: input.isSecret,
452
+ }
453
+ : variable);
454
+ this.mockData.environmentVariables.set(environmentId, updated);
455
+ return { success: true };
456
+ }
457
+ }
458
+ throw new ORPCError("NOT_FOUND", {
459
+ message: "Environment variable not found",
460
+ });
461
+ }),
462
+ },
463
+ delete: {
464
+ v1: implement(contract.environmentVariables.delete.v1).handler(async ({ input }) => {
465
+ for (const [environmentId, variables] of this.mockData.environmentVariables.entries()) {
466
+ const index = variables.findIndex((variable) => variable.id === input.environmentVariableId);
467
+ if (index !== -1) {
468
+ this.mockData.environmentVariables.set(environmentId, variables.filter((_, i) => i !== index));
469
+ return { success: true };
470
+ }
471
+ }
472
+ throw new ORPCError("NOT_FOUND", {
473
+ message: "Environment variable not found",
474
+ });
475
+ }),
476
+ },
477
+ },
478
+ distribution: {
479
+ get: {
480
+ v1: implement(contract.distribution.get.v1).handler(async ({ input }) => {
481
+ const existing = this.mockData.publishedServers.get(`${input.projectId}:${input.domain}`);
482
+ return {
483
+ serverFields: existing ?? {
484
+ $schema: "https://registry.modelcontextprotocol.io/schemas/1.0/server.json",
485
+ name: "",
486
+ description: "",
487
+ },
488
+ };
489
+ }),
490
+ },
491
+ publish: {
492
+ v1: implement(contract.distribution.publish.v1).handler(async ({ input }) => {
493
+ const project = this.mockData.projects.get(input.projectId);
494
+ if (!project) {
495
+ throw new ORPCError("NOT_FOUND", {
496
+ message: "Project not found",
497
+ });
498
+ }
499
+ const key = `${input.projectId}:${input.domain}`;
500
+ const existing = this.mockData.publishedServers.get(key);
501
+ const version = existing?.version ? (semverInc(existing.version, "patch") ?? "0.0.1") : "0.0.1";
502
+ const serverFields = {
503
+ $schema: "https://registry.modelcontextprotocol.io/schemas/1.0/server.json",
504
+ name: input.domain,
505
+ description: input.description,
506
+ title: input.title,
507
+ version,
508
+ ...(input.websiteUrl ? { websiteUrl: input.websiteUrl } : {}),
509
+ ...(input.iconSrc ? { icons: [{ src: input.iconSrc }] } : {}),
510
+ };
511
+ if (!input.dryRun) {
512
+ this.mockData.publishedServers.set(key, serverFields);
513
+ }
514
+ return { serverFields };
515
+ }),
516
+ },
517
+ },
518
+ beacon: {
519
+ create: {
520
+ v1: implement(contract.beacon.create.v1).handler(async () => {
521
+ return { id: "mock-analysis-id" };
522
+ }),
523
+ },
524
+ get: {
525
+ v1: implement(contract.beacon.get.v1).handler(async () => {
526
+ if (this.mockData.beaconGetResponses.length > 0) {
527
+ const index = Math.min(this.mockData.beaconGetCallCount, this.mockData.beaconGetResponses.length - 1);
528
+ this.mockData.beaconGetCallCount++;
529
+ const response = this.mockData.beaconGetResponses[index];
530
+ if (!response) {
531
+ throw new Error("Missing mocked beacon.get response");
532
+ }
533
+ return response;
534
+ }
535
+ return {
536
+ id: "mock-analysis-id",
537
+ targetUrl: "https://example.com/mcp",
538
+ status: "completed",
539
+ durationMs: 1234,
540
+ createdAt: new Date(),
541
+ report: {
542
+ schemaVersion: "v20260224",
543
+ auditId: "mock-analysis-id",
544
+ targetUrl: "https://example.com/mcp",
545
+ startedAt: new Date().toISOString(),
546
+ completedAt: new Date().toISOString(),
547
+ durationMs: 1234,
548
+ results: [],
549
+ requiresAuth: false,
550
+ hasViewSupport: false,
551
+ isReadyForPlatform: { chatgpt: true, claudeai: true },
552
+ },
553
+ };
554
+ }),
555
+ },
556
+ },
282
557
  deployments: {
283
558
  list: {
284
559
  v1: implement(contract.deployments.list.v1).handler(async ({ input }) => {
285
560
  const project = this.mockData.projects.get(input.projectId);
286
561
  if (!project) {
287
- throw new ORPCError("NOT_FOUND", { message: "Project not found" });
562
+ throw new ORPCError("NOT_FOUND", {
563
+ message: "Project not found",
564
+ });
565
+ }
566
+ let deployments = Array.from(this.mockData.deployments.values());
567
+ if (input.environmentId) {
568
+ deployments = deployments.filter((deployment) => deployment.environmentId === input.environmentId);
288
569
  }
289
- return Array.from(this.mockData.deployments.values()).map((deployment) => ({
570
+ if (input.status?.length) {
571
+ deployments = deployments.filter((deployment) => input.status.includes(deployment.status));
572
+ }
573
+ return deployments.map((deployment) => ({
290
574
  ...deployment,
291
575
  isCurrent: deployment.status === "deployed",
292
- environmentId: project.productionEnvironment?.id ?? "",
293
576
  }));
294
577
  }),
295
578
  },
@@ -297,13 +580,16 @@ export class MockApiServer {
297
580
  v1: implement(contract.deployments.get.v1).handler(async ({ input }) => {
298
581
  const deployment = this.mockData.deployments.get(input.deploymentId);
299
582
  if (!deployment) {
300
- throw new ORPCError("NOT_FOUND", { message: "Deployment not found" });
583
+ throw new ORPCError("NOT_FOUND", {
584
+ message: "Deployment not found",
585
+ });
301
586
  }
302
587
  if (deployment.status === "ongoing") {
303
588
  const updatedDeployment = {
304
589
  ...deployment,
305
590
  status: "deployed",
306
591
  completedAt: new Date(),
592
+ deploymentPageUrl: deployment.deploymentPageUrl ?? null,
307
593
  };
308
594
  this.mockData.deployments.set(input.deploymentId, updatedDeployment);
309
595
  return updatedDeployment;
@@ -315,7 +601,16 @@ export class MockApiServer {
315
601
  v1: implement(contract.deployments.getLogs.v1).handler(async ({ input }) => {
316
602
  const deployment = this.mockData.deployments.get(input.deploymentId);
317
603
  if (!deployment) {
318
- throw new ORPCError("NOT_FOUND", { message: "Deployment not found" });
604
+ throw new ORPCError("NOT_FOUND", {
605
+ message: "Deployment not found",
606
+ });
607
+ }
608
+ const pages = this.mockData.deploymentLogPages.get(input.deploymentId);
609
+ if (pages !== undefined) {
610
+ const callCount = this.mockData.deploymentLogCallCounts.get(input.deploymentId) ?? 0;
611
+ const pageIndex = Math.min(callCount, pages.length - 1);
612
+ this.mockData.deploymentLogCallCounts.set(input.deploymentId, callCount + 1);
613
+ return pages[pageIndex] ?? { logs: [], hasMoreLogs: false };
319
614
  }
320
615
  return {
321
616
  logs: [{ timestamp: new Date(), content: "Mock log entry" }],
@@ -326,7 +621,7 @@ export class MockApiServer {
326
621
  uploadArtifact: {
327
622
  v1: implement(contract.deployments.uploadArtifact.v1).handler(async () => {
328
623
  const token = randomUUID();
329
- const baseUrl = `http://127.0.0.1:${this.port}`;
624
+ const baseUrl = `https://127.0.0.1:${this.port}`;
330
625
  const uploadUrl = `${baseUrl}/__mock__upload/${token}`;
331
626
  return {
332
627
  uploadUrl,
@@ -344,7 +639,12 @@ export class MockApiServer {
344
639
  path: req.path,
345
640
  timestamp: new Date(),
346
641
  };
347
- if (req.body) {
642
+ if (req.method === "GET") {
643
+ if (Object.keys(req.query).length > 0) {
644
+ apiCall.input = req.query;
645
+ }
646
+ }
647
+ else if (req.body) {
348
648
  apiCall.input = req.body;
349
649
  }
350
650
  const result = await handler.handle(req, res, {
@@ -360,6 +660,23 @@ export class MockApiServer {
360
660
  this.callHistory.push(apiCall);
361
661
  });
362
662
  }
663
+ reset() {
664
+ this.callHistory = [];
665
+ this.mockData = {
666
+ teams: null,
667
+ projects: new Map(),
668
+ environments: new Map(),
669
+ deployments: new Map(),
670
+ environmentVariables: new Map(),
671
+ runtimeLogs: new Map(),
672
+ runtimeLogPages: new Map(),
673
+ deploymentLogPages: new Map(),
674
+ deploymentLogCallCounts: new Map(),
675
+ publishedServers: new Map(),
676
+ beaconGetResponses: [],
677
+ beaconGetCallCount: 0,
678
+ };
679
+ }
363
680
  async stop() {
364
681
  return new Promise((resolve, reject) => {
365
682
  if (!this.server) {
@@ -398,6 +715,8 @@ export class MockApiServer {
398
715
  getLastCall(method, pathPattern) {
399
716
  for (let i = this.callHistory.length - 1; i >= 0; i--) {
400
717
  const call = this.callHistory[i];
718
+ if (!call)
719
+ continue;
401
720
  if (method && call.method !== method)
402
721
  continue;
403
722
  if (pathPattern) {
@@ -414,6 +733,43 @@ export class MockApiServer {
414
733
  }
415
734
  return undefined;
416
735
  }
736
+ setTeams(teams) {
737
+ this.mockData.teams = teams.map((team) => ({
738
+ ...team,
739
+ createdAt: new Date(),
740
+ hasStripeAccount: false,
741
+ }));
742
+ }
743
+ addEnvironmentVariables(environmentId, variables) {
744
+ this.mockData.environmentVariables.set(environmentId, variables);
745
+ }
746
+ setRuntimeLogs(environmentId, logs) {
747
+ this.mockData.runtimeLogs.set(environmentId, logs);
748
+ this.mockData.runtimeLogPages.delete(environmentId);
749
+ }
750
+ setRuntimeLogPages(environmentId, pages) {
751
+ this.mockData.runtimeLogPages.set(environmentId, pages);
752
+ this.mockData.runtimeLogs.delete(environmentId);
753
+ }
754
+ setPublishedServer(projectId, domain, server) {
755
+ this.mockData.publishedServers.set(`${projectId}:${domain}`, server);
756
+ }
757
+ addDeployments(deployments) {
758
+ for (const deployment of deployments) {
759
+ this.mockData.deployments.set(deployment.id, {
760
+ ...deployment,
761
+ deploymentPageUrl: null,
762
+ });
763
+ }
764
+ }
765
+ setDeploymentLogPages(deploymentId, pages) {
766
+ this.mockData.deploymentLogPages.set(deploymentId, pages);
767
+ this.mockData.deploymentLogCallCounts.set(deploymentId, 0);
768
+ }
769
+ setBeaconGetResponses(responses) {
770
+ this.mockData.beaconGetResponses = responses;
771
+ this.mockData.beaconGetCallCount = 0;
772
+ }
417
773
  addProject(project) {
418
774
  this.mockData.projects.set(project.id, project);
419
775
  if (project.productionEnvironment) {