@sequenceholdings/artifact-studio 0.2.6 → 0.3.1

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 (190) hide show
  1. package/VERSION-PIN.md +12 -1
  2. package/dist/api.d.ts +11 -1
  3. package/dist/api.js +26 -9
  4. package/dist/build.d.ts +2 -2
  5. package/dist/build.js +4 -0
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +134 -13
  8. package/dist/dev-link.d.ts +1 -1
  9. package/dist/dev-link.js +2 -1
  10. package/dist/gateway/admission.d.ts +56 -0
  11. package/dist/gateway/admission.js +134 -0
  12. package/dist/gateway/authorization-cache.d.ts +52 -0
  13. package/dist/gateway/authorization-cache.js +111 -0
  14. package/dist/gateway/control-admission.d.ts +48 -0
  15. package/dist/gateway/control-admission.js +127 -0
  16. package/dist/gateway/control.d.ts +94 -0
  17. package/dist/gateway/control.js +116 -0
  18. package/dist/gateway/hosted-proxy.d.ts +36 -0
  19. package/dist/gateway/hosted-proxy.js +344 -0
  20. package/dist/gateway/release-admission.d.ts +23 -0
  21. package/dist/gateway/release-admission.js +259 -0
  22. package/dist/gateway/server.d.ts +38 -0
  23. package/dist/gateway/server.js +382 -0
  24. package/dist/gateway/session.d.ts +72 -0
  25. package/dist/gateway/session.js +144 -0
  26. package/dist/gateway/usage.d.ts +70 -0
  27. package/dist/gateway/usage.js +116 -0
  28. package/dist/gateway-main.d.ts +1 -0
  29. package/dist/gateway-main.js +114 -0
  30. package/dist/git-clone.d.ts +23 -1
  31. package/dist/git-clone.js +32 -15
  32. package/dist/manifest-next.d.ts +217 -0
  33. package/dist/manifest-next.js +132 -0
  34. package/dist/manifest-static.d.ts +76 -0
  35. package/dist/manifest-static.js +124 -0
  36. package/dist/manifest.d.ts +118 -13
  37. package/dist/manifest.js +12 -119
  38. package/dist/next/bridge.d.ts +47 -0
  39. package/dist/next/bridge.js +236 -0
  40. package/dist/next/browser-logs.d.ts +7 -0
  41. package/dist/next/browser-logs.js +51 -0
  42. package/dist/next/browser.d.ts +6 -0
  43. package/dist/next/browser.js +57 -0
  44. package/dist/next/protocol.d.ts +73 -0
  45. package/dist/next/protocol.js +56 -0
  46. package/dist/next/release.d.ts +15 -0
  47. package/dist/next/release.js +46 -0
  48. package/dist/next/requests.d.ts +15 -0
  49. package/dist/next/requests.js +59 -0
  50. package/dist/next/routes.d.ts +4 -0
  51. package/dist/next/routes.js +21 -0
  52. package/dist/next/streams.d.ts +16 -0
  53. package/dist/next/streams.js +132 -0
  54. package/dist/next/theme.d.ts +10 -0
  55. package/dist/next/theme.js +45 -0
  56. package/dist/next/transport.d.ts +15 -0
  57. package/dist/next/transport.js +13 -0
  58. package/dist/next-build.d.ts +29 -0
  59. package/dist/next-build.js +129 -0
  60. package/dist/next-cache/adapter-runtime.d.ts +51 -0
  61. package/dist/next-cache/adapter-runtime.js +73 -0
  62. package/dist/next-cache/component-handler.d.ts +3 -0
  63. package/dist/next-cache/component-handler.js +10 -0
  64. package/dist/next-cache/components.d.ts +20 -0
  65. package/dist/next-cache/components.js +113 -0
  66. package/dist/next-cache/config.d.ts +9 -0
  67. package/dist/next-cache/config.js +105 -0
  68. package/dist/next-cache/incremental-handler.d.ts +18 -0
  69. package/dist/next-cache/incremental-handler.js +17 -0
  70. package/dist/next-cache/incremental.d.ts +27 -0
  71. package/dist/next-cache/incremental.js +99 -0
  72. package/dist/next-cache/index.d.ts +18 -0
  73. package/dist/next-cache/index.js +16 -0
  74. package/dist/next-cache/memory-store.d.ts +27 -0
  75. package/dist/next-cache/memory-store.js +118 -0
  76. package/dist/next-cache/redis-scripts.d.ts +3 -0
  77. package/dist/next-cache/redis-scripts.js +85 -0
  78. package/dist/next-cache/redis-store.d.ts +18 -0
  79. package/dist/next-cache/redis-store.js +56 -0
  80. package/dist/next-cache/runtime.d.ts +29 -0
  81. package/dist/next-cache/runtime.js +63 -0
  82. package/dist/next-cache/serialization.d.ts +39 -0
  83. package/dist/next-cache/serialization.js +333 -0
  84. package/dist/next-cache/store.d.ts +54 -0
  85. package/dist/next-cache/store.js +45 -0
  86. package/dist/next-cache/valkey-transport.d.ts +52 -0
  87. package/dist/next-cache/valkey-transport.js +189 -0
  88. package/dist/next-dev-control.d.ts +48 -0
  89. package/dist/next-dev-control.js +63 -0
  90. package/dist/next-dev-process.d.ts +17 -0
  91. package/dist/next-dev-process.js +110 -0
  92. package/dist/next-dev.d.ts +12 -0
  93. package/dist/next-dev.js +267 -0
  94. package/dist/next-image-config.d.ts +9 -0
  95. package/dist/next-image-config.js +101 -0
  96. package/dist/next-project.d.ts +22 -0
  97. package/dist/next-project.js +64 -0
  98. package/dist/next-public-fonts-local.d.ts +7 -0
  99. package/dist/next-public-fonts-local.js +64 -0
  100. package/dist/next-public-fonts.d.ts +13 -0
  101. package/dist/next-public-fonts.js +49 -0
  102. package/dist/next-source-hash.d.ts +14 -0
  103. package/dist/next-source-hash.js +18 -0
  104. package/dist/next-source.d.ts +35 -0
  105. package/dist/next-source.js +155 -0
  106. package/dist/next.d.ts +9 -0
  107. package/dist/next.js +5 -0
  108. package/dist/project.d.ts +3 -0
  109. package/dist/project.js +9 -1
  110. package/dist/runtime-contracts.d.ts +229 -0
  111. package/dist/runtime-contracts.js +224 -0
  112. package/dist/sdk.d.ts +2 -2
  113. package/dist/server/broker-contract.d.ts +68 -0
  114. package/dist/server/broker-contract.js +33 -0
  115. package/dist/server/client.d.ts +81 -0
  116. package/dist/server/client.js +159 -0
  117. package/dist/server/logger.d.ts +26 -0
  118. package/dist/server/logger.js +24 -0
  119. package/dist/server/release-forms.d.ts +13 -0
  120. package/dist/server/release-forms.js +124 -0
  121. package/dist/server/request-context.d.ts +75 -0
  122. package/dist/server/request-context.js +107 -0
  123. package/dist/server/runtime-config.d.ts +54 -0
  124. package/dist/server/runtime-config.js +64 -0
  125. package/dist/server.d.ts +8 -0
  126. package/dist/server.js +4 -0
  127. package/dist/templates/nextjs/.artifactignore +14 -0
  128. package/dist/templates/nextjs/README.md +157 -0
  129. package/dist/templates/nextjs/app/about/page.tsx +26 -0
  130. package/dist/templates/nextjs/app/actions.ts +18 -0
  131. package/dist/templates/nextjs/app/api/health/route.ts +6 -0
  132. package/dist/templates/nextjs/app/components/counter.tsx +19 -0
  133. package/dist/templates/nextjs/app/components/greeting-form.tsx +38 -0
  134. package/dist/templates/nextjs/app/globals.css +4 -0
  135. package/dist/templates/nextjs/app/layout.tsx +27 -0
  136. package/dist/templates/nextjs/app/page.tsx +51 -0
  137. package/dist/templates/nextjs/app/providers.tsx +107 -0
  138. package/dist/templates/nextjs/artifact.bundle.yml +55 -0
  139. package/dist/templates/nextjs/gitignore +9 -0
  140. package/dist/templates/nextjs/next.config.mjs +27 -0
  141. package/dist/templates/nextjs/package.json +32 -0
  142. package/dist/templates/nextjs/pnpm-workspace.yaml +22 -0
  143. package/dist/templates/nextjs/postcss.config.mjs +5 -0
  144. package/dist/templates/nextjs/tsconfig.json +20 -0
  145. package/dist/templates/react-vite/package.json +1 -1
  146. package/dist/trusted-install.js +62 -2
  147. package/dist/tunnel/broker.d.ts +75 -0
  148. package/dist/tunnel/broker.js +463 -0
  149. package/dist/tunnel/client.d.ts +22 -0
  150. package/dist/tunnel/client.js +177 -0
  151. package/dist/tunnel/index.d.ts +6 -0
  152. package/dist/tunnel/index.js +4 -0
  153. package/dist/tunnel/io.d.ts +33 -0
  154. package/dist/tunnel/io.js +108 -0
  155. package/dist/tunnel/peer.d.ts +25 -0
  156. package/dist/tunnel/peer.js +147 -0
  157. package/dist/tunnel/protocol.d.ts +78 -0
  158. package/dist/tunnel/protocol.js +210 -0
  159. package/dist/tunnel/redis-auth.d.ts +37 -0
  160. package/dist/tunnel/redis-auth.js +92 -0
  161. package/dist/tunnel/redis-ownership.d.ts +38 -0
  162. package/dist/tunnel/redis-ownership.js +100 -0
  163. package/dist/tunnel/redis-socket.d.ts +13 -0
  164. package/dist/tunnel/redis-socket.js +48 -0
  165. package/dist/tunnel/redis-types.d.ts +75 -0
  166. package/dist/tunnel/redis-types.js +9 -0
  167. package/dist/tunnel/redis.d.ts +56 -0
  168. package/dist/tunnel/redis.js +404 -0
  169. package/dist/tunnel/transport.d.ts +17 -0
  170. package/dist/tunnel/transport.js +1 -0
  171. package/package.json +67 -4
  172. package/templates/nextjs/.artifactignore +14 -0
  173. package/templates/nextjs/README.md +157 -0
  174. package/templates/nextjs/app/about/page.tsx +26 -0
  175. package/templates/nextjs/app/actions.ts +18 -0
  176. package/templates/nextjs/app/api/health/route.ts +6 -0
  177. package/templates/nextjs/app/components/counter.tsx +19 -0
  178. package/templates/nextjs/app/components/greeting-form.tsx +38 -0
  179. package/templates/nextjs/app/globals.css +4 -0
  180. package/templates/nextjs/app/layout.tsx +27 -0
  181. package/templates/nextjs/app/page.tsx +51 -0
  182. package/templates/nextjs/app/providers.tsx +107 -0
  183. package/templates/nextjs/artifact.bundle.yml +55 -0
  184. package/templates/nextjs/gitignore +9 -0
  185. package/templates/nextjs/next.config.mjs +27 -0
  186. package/templates/nextjs/package.json +32 -0
  187. package/templates/nextjs/pnpm-workspace.yaml +22 -0
  188. package/templates/nextjs/postcss.config.mjs +5 -0
  189. package/templates/nextjs/tsconfig.json +20 -0
  190. package/templates/react-vite/package.json +1 -1
@@ -0,0 +1,70 @@
1
+ export interface GatewayUsageEvent {
2
+ eventId: string;
3
+ requestId: string;
4
+ /** The user the gateway acted for: the session owner Atlas authorized for this request. */
5
+ actorId: string;
6
+ projectId: string;
7
+ deploymentId?: string;
8
+ devSessionId?: string;
9
+ environment: string;
10
+ startedAt: string;
11
+ endedAt: string;
12
+ measurements: {
13
+ requestCount: 1;
14
+ durationMs: number;
15
+ };
16
+ outcome: 'success' | 'error' | 'cancelled';
17
+ httpStatus: number;
18
+ }
19
+ export type GatewayUsageFailure = {
20
+ kind: 'usage_queue_full' | 'usage_delivery_failed' | 'usage_delivery_rejected' | 'usage_events_rejected' | 'usage_lost_on_shutdown';
21
+ count: number;
22
+ };
23
+ export interface GatewayUsageLimits {
24
+ /** Events buffered or in delivery; later observations are dropped and counted. */
25
+ capacity: number;
26
+ /** Events per control-plane call. Atlas accepts at most this many per batch. */
27
+ maxBatch: number;
28
+ /** Longest an observation waits in the buffer before a partial batch is sent. */
29
+ flushIntervalMs: number;
30
+ /** Concurrent batch deliveries. */
31
+ concurrency: number;
32
+ }
33
+ export declare const GATEWAY_USAGE_LIMITS: Readonly<GatewayUsageLimits>;
34
+ /**
35
+ * Batched, bounded delivery of request observations: one control-plane call per `maxBatch` events or
36
+ * per flush interval, instead of one per request. Atlas deduplicates accepted records durably, so a
37
+ * retried batch is idempotent. Observations are telemetry, not billing totals. A crash loses at most
38
+ * the unflushed buffer (about one flush interval) and the batches in flight; a graceful drain flushes
39
+ * everything, and a forced exit reports `pending` as `usage_lost_on_shutdown`.
40
+ */
41
+ export declare class GatewayUsageReporter {
42
+ private readonly options;
43
+ private readonly buffer;
44
+ private readonly inFlight;
45
+ private readonly limits;
46
+ private timer;
47
+ private dropped;
48
+ private closed;
49
+ constructor(options: {
50
+ send: (events: GatewayUsageEvent[]) => Promise<{
51
+ rejected: number;
52
+ }>;
53
+ onFailure: (failure: GatewayUsageFailure) => void;
54
+ limits?: Partial<GatewayUsageLimits>;
55
+ });
56
+ /** Observations accepted but not yet confirmed by Atlas. */
57
+ get pending(): number;
58
+ record(event: GatewayUsageEvent): void;
59
+ /**
60
+ * Stops accepting observations and delivers everything buffered; each batch is bounded by its
61
+ * retries. A process that must exit sooner reports `pending` as lost (see gateway-main).
62
+ */
63
+ close(): Promise<void>;
64
+ private drainAll;
65
+ private scheduleFlush;
66
+ private reportDropped;
67
+ /** Sends full batches, and a partial batch too when forced by the timer or shutdown. */
68
+ private pump;
69
+ private deliver;
70
+ }
@@ -0,0 +1,116 @@
1
+ export const GATEWAY_USAGE_LIMITS = Object.freeze({ capacity: 10_000, maxBatch: 100, flushIntervalMs: 1_000, concurrency: 4 });
2
+ const ATTEMPTS = 3;
3
+ /**
4
+ * Batched, bounded delivery of request observations: one control-plane call per `maxBatch` events or
5
+ * per flush interval, instead of one per request. Atlas deduplicates accepted records durably, so a
6
+ * retried batch is idempotent. Observations are telemetry, not billing totals. A crash loses at most
7
+ * the unflushed buffer (about one flush interval) and the batches in flight; a graceful drain flushes
8
+ * everything, and a forced exit reports `pending` as `usage_lost_on_shutdown`.
9
+ */
10
+ export class GatewayUsageReporter {
11
+ options;
12
+ buffer = [];
13
+ inFlight = new Map();
14
+ limits;
15
+ timer;
16
+ dropped = 0;
17
+ closed = false;
18
+ constructor(options) {
19
+ this.options = options;
20
+ this.limits = { ...GATEWAY_USAGE_LIMITS, ...options.limits };
21
+ for (const value of Object.values(this.limits)) {
22
+ if (!Number.isSafeInteger(value) || value < 1)
23
+ throw new Error('Gateway usage limits must be positive integers');
24
+ }
25
+ }
26
+ /** Observations accepted but not yet confirmed by Atlas. */
27
+ get pending() {
28
+ let count = this.buffer.length;
29
+ for (const size of this.inFlight.values())
30
+ count += size;
31
+ return count;
32
+ }
33
+ record(event) {
34
+ if (this.closed) {
35
+ this.options.onFailure({ kind: 'usage_lost_on_shutdown', count: 1 });
36
+ return;
37
+ }
38
+ if (this.pending >= this.limits.capacity) {
39
+ this.dropped++;
40
+ this.scheduleFlush();
41
+ return;
42
+ }
43
+ this.buffer.push(event);
44
+ if (this.buffer.length >= this.limits.maxBatch)
45
+ this.pump();
46
+ else
47
+ this.scheduleFlush();
48
+ }
49
+ /**
50
+ * Stops accepting observations and delivers everything buffered; each batch is bounded by its
51
+ * retries. A process that must exit sooner reports `pending` as lost (see gateway-main).
52
+ */
53
+ async close() {
54
+ this.closed = true;
55
+ clearTimeout(this.timer);
56
+ this.timer = undefined;
57
+ await this.drainAll();
58
+ this.reportDropped();
59
+ }
60
+ async drainAll() {
61
+ while (this.buffer.length || this.inFlight.size) {
62
+ this.pump({ force: true });
63
+ if (this.inFlight.size)
64
+ await Promise.race(this.inFlight.keys());
65
+ }
66
+ }
67
+ scheduleFlush() {
68
+ if (this.timer || this.closed)
69
+ return;
70
+ this.timer = setTimeout(() => {
71
+ this.timer = undefined;
72
+ this.reportDropped();
73
+ this.pump({ force: true });
74
+ if (this.buffer.length)
75
+ this.scheduleFlush();
76
+ }, this.limits.flushIntervalMs);
77
+ this.timer.unref();
78
+ }
79
+ reportDropped() {
80
+ if (!this.dropped)
81
+ return;
82
+ this.options.onFailure({ kind: 'usage_queue_full', count: this.dropped });
83
+ this.dropped = 0;
84
+ }
85
+ /** Sends full batches, and a partial batch too when forced by the timer or shutdown. */
86
+ pump({ force = false } = {}) {
87
+ while (this.inFlight.size < this.limits.concurrency && (this.buffer.length >= this.limits.maxBatch || (force && this.buffer.length))) {
88
+ const batch = this.buffer.splice(0, this.limits.maxBatch);
89
+ const delivery = this.deliver(batch).finally(() => {
90
+ this.inFlight.delete(delivery);
91
+ this.pump({ force: this.closed });
92
+ });
93
+ this.inFlight.set(delivery, batch.length);
94
+ }
95
+ }
96
+ async deliver(batch) {
97
+ for (let attempt = 0; attempt < ATTEMPTS; attempt++) {
98
+ try {
99
+ const { rejected } = await this.options.send(batch);
100
+ if (rejected > 0)
101
+ this.options.onFailure({ kind: 'usage_events_rejected', count: rejected });
102
+ return;
103
+ }
104
+ catch (error) {
105
+ const status = typeof error === 'object' && error !== null && 'status' in error ? error.status : undefined;
106
+ if (typeof status === 'number' && status >= 400 && status < 500 && status !== 429) {
107
+ this.options.onFailure({ kind: 'usage_delivery_rejected', count: batch.length });
108
+ return;
109
+ }
110
+ if (attempt < ATTEMPTS - 1)
111
+ await new Promise((resolve) => setTimeout(resolve, 100 * 2 ** attempt));
112
+ }
113
+ }
114
+ this.options.onFailure({ kind: 'usage_delivery_failed', count: batch.length });
115
+ }
116
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,114 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { isIP } from 'node:net';
3
+ import { Redis } from 'ioredis';
4
+ import { ArtifactGatewayControl } from './gateway/control.js';
5
+ import { createArtifactGateway } from './gateway/server.js';
6
+ import { RedisTunnelOwnership, RedisTunnelRelay } from './tunnel/redis.js';
7
+ import { ArtifactRequestAdmission } from './gateway/admission.js';
8
+ import { GatewayControlAdmission } from './gateway/control-admission.js';
9
+ function required(name) {
10
+ const value = process.env[name];
11
+ if (!value)
12
+ throw new Error(`Missing required gateway configuration: ${name}`);
13
+ return value;
14
+ }
15
+ /** A separate process/identity from Atlas and all application/build containers. */
16
+ async function main() {
17
+ if (process.env.ARTIFACT_NEXT_ENABLED !== 'true')
18
+ throw new Error('Artifact Next gateway is disabled');
19
+ const redisUrl = required('ARTIFACT_NEXT_REDIS_URL');
20
+ if (new URL(redisUrl).protocol !== 'rediss:')
21
+ throw new Error('Artifact tunnel routing requires private Redis with TLS');
22
+ const namespace = required('APP_ENV'), nodeId = randomUUID();
23
+ const redisOptions = {
24
+ enableOfflineQueue: false, autoResendUnfulfilledCommands: false, maxRetriesPerRequest: 0,
25
+ lazyConnect: true, connectTimeout: 10_000,
26
+ tls: { rejectUnauthorized: true, ...(process.env.REDIS_CA_CERT ? { ca: process.env.REDIS_CA_CERT } : {}) },
27
+ };
28
+ const command = new Redis(redisUrl, redisOptions);
29
+ const publisher = new Redis(redisUrl, redisOptions);
30
+ const subscriber = new Redis(redisUrl, { ...redisOptions, autoResubscribe: false });
31
+ const clients = [command, publisher, subscriber];
32
+ for (const client of clients)
33
+ client.on('error', () => {
34
+ // Provider messages may include connection strings; keep them out of logs.
35
+ process.stderr.write(`${JSON.stringify({ event: 'artifact_gateway.redis_unavailable', nodeId })}\n`);
36
+ });
37
+ await Promise.all(clients.map(client => client.connect()));
38
+ const log = (event) => process.stderr.write(`${JSON.stringify({ ...event, nodeId })}\n`);
39
+ // A flood of refused requests or forged frames must not become a log flood: count and report every 10 s.
40
+ const refusals = new Map();
41
+ const refuse = (kind) => { refusals.set(kind, (refusals.get(kind) ?? 0) + 1); };
42
+ setInterval(() => {
43
+ for (const [kind, count] of refusals)
44
+ log({ event: 'artifact_gateway.request', kind, count });
45
+ refusals.clear();
46
+ }, 10_000).unref();
47
+ const sessionKey = required('ARTIFACT_NEXT_SESSION_KEY');
48
+ const ownership = new RedisTunnelOwnership({ command, namespace });
49
+ // Relay frames are authenticated with a subkey of the gateway-only session key, which every
50
+ // replica already shares and Atlas never holds.
51
+ const relay = new RedisTunnelRelay({ nodeId, namespace, ownership, publisher, subscriber, secret: sessionKey,
52
+ onRejectedFrame: () => refuse('relay_frame_rejected') });
53
+ await relay.start();
54
+ let stopping = false;
55
+ subscriber.on('ready', () => { if (!stopping)
56
+ void relay.start().catch(() => undefined); });
57
+ const controlUrl = required('ARTIFACT_NEXT_CONTROL_PLANE_URL');
58
+ const gateway = createArtifactGateway({
59
+ control: new ArtifactGatewayControl({ url: controlUrl, token: required('ARTIFACT_NEXT_CONTROL_TOKEN') }),
60
+ runtimeDomain: required('ARTIFACT_NEXT_RUNTIME_DOMAIN'),
61
+ parentOrigin: process.env.ARTIFACT_NEXT_PARENT_ORIGIN ?? new URL(controlUrl).origin,
62
+ gatewayOrigin: required('ARTIFACT_NEXT_GATEWAY_URL'),
63
+ sessionKey,
64
+ contextPrivateKey: required('ARTIFACT_NEXT_CONTEXT_PRIVATE_KEY'),
65
+ contextKeyId: required('ARTIFACT_NEXT_CONTEXT_KEY_ID'),
66
+ nodeId, routing: ownership, relay,
67
+ admission: new ArtifactRequestAdmission({ redis: command, namespace }),
68
+ controlAdmission: new GatewayControlAdmission({ redis: command, namespace }),
69
+ resolveLease: sessionId => ownership.resolveLease(sessionId),
70
+ renewLease: lease => ownership.renew(lease, nodeId),
71
+ ready: () => clients.every(client => client.status === 'ready') && relay.isReady(),
72
+ onError: event => { if (event.kind === 'admission_rejected')
73
+ refuse(event.kind);
74
+ else
75
+ log({ event: 'artifact_gateway.request', ...event }); },
76
+ });
77
+ const port = Number(process.env.PORT ?? '8080');
78
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535)
79
+ throw new Error('Invalid gateway PORT');
80
+ // Every interface in its container; a laptop run (scripts/local-stack.sh) keeps it on loopback.
81
+ const host = process.env.ARTIFACT_NEXT_GATEWAY_HOST ?? '0.0.0.0';
82
+ if (isIP(host) === 0)
83
+ throw new Error('Invalid ARTIFACT_NEXT_GATEWAY_HOST: expected an IP address');
84
+ await new Promise((resolve, reject) => {
85
+ gateway.server.once('error', reject);
86
+ gateway.server.listen(port, host, resolve);
87
+ });
88
+ process.stdout.write(`${JSON.stringify({ event: 'artifact_gateway.ready', nodeId, port })}\n`);
89
+ const stop = async () => {
90
+ if (stopping)
91
+ return;
92
+ stopping = true;
93
+ const deadline = setTimeout(() => {
94
+ // Account for observations a forced exit discards; a graceful drain reports its own losses.
95
+ const lost = gateway.pendingUsage;
96
+ if (lost)
97
+ log({ event: 'artifact_gateway.request', kind: 'usage_lost_on_shutdown', count: lost });
98
+ gateway.server.closeAllConnections();
99
+ process.exit(1);
100
+ }, 9_000);
101
+ deadline.unref();
102
+ await gateway.drain();
103
+ await relay.close();
104
+ for (const client of clients)
105
+ client.disconnect();
106
+ clearTimeout(deadline);
107
+ };
108
+ process.once('SIGTERM', () => { void stop(); });
109
+ process.once('SIGINT', () => { void stop(); });
110
+ }
111
+ void main().catch(() => {
112
+ process.stderr.write(`${JSON.stringify({ event: 'artifact_gateway.start_failed' })}\n`);
113
+ process.exit(1);
114
+ });
@@ -15,11 +15,29 @@ export type RunGitClone = (args: {
15
15
  destDir: string;
16
16
  /** Optional branch, tag, or commit SHA. */
17
17
  ref?: string;
18
+ /**
19
+ * `git clone --depth`. Omitted for commit-SHA refs: those clone the
20
+ * default branch in full, then check the SHA out.
21
+ */
22
+ depth?: number;
18
23
  /** PAT used as Basic-auth password (username `git`). */
19
24
  pat: string;
20
25
  /** Hard ceiling before the git child is killed (default 5m). */
21
26
  timeoutMs?: number;
22
27
  }) => Promise<void>;
28
+ /**
29
+ * Argv for `git clone`, excluding the askpass environment.
30
+ * Positive `depth` is a shallow tip clone. Commit SHAs ignore `depth`.
31
+ */
32
+ export declare function gitCloneArgs({ cloneUrl, destDir, ref, depth, }: {
33
+ cloneUrl: string;
34
+ destDir: string;
35
+ ref?: string;
36
+ depth?: number;
37
+ }): {
38
+ args: string[];
39
+ shaRef?: string;
40
+ };
23
41
  /**
24
42
  * Run `fn` with GIT_ASKPASS set to a short-lived script that answers username
25
43
  * with `git` and password with `pat`. The script is deleted afterward.
@@ -31,11 +49,15 @@ export declare function withGitAskpass<T>({ pat, fn, }: {
31
49
  /**
32
50
  * Clone with a clean URL + PAT via askpass. Branch/tag refs use `--branch`;
33
51
  * commit SHAs clone default HEAD then `git checkout <sha>`.
52
+ * `depth` (repos clone passes 1) fetches only that many commits — a full
53
+ * history walk against git-service reads every object from object storage
54
+ * before the pack starts, which stalls large repos.
34
55
  */
35
- export declare function runGitClone({ cloneUrl, destDir, ref, pat, timeoutMs, }: {
56
+ export declare function runGitClone({ cloneUrl, destDir, ref, depth, pat, timeoutMs, }: {
36
57
  cloneUrl: string;
37
58
  destDir: string;
38
59
  ref?: string;
60
+ depth?: number;
39
61
  pat: string;
40
62
  timeoutMs?: number;
41
63
  }): Promise<void>;
package/dist/git-clone.js CHANGED
@@ -55,6 +55,28 @@ const GIT_LOW_SPEED_LIMIT_BYTES = 1000;
55
55
  const GIT_LOW_SPEED_TIME_SEC = 180;
56
56
  /** Backstop hard deadline: SIGKILL the child even if it trickles above the low-speed floor. */
57
57
  const GIT_CLONE_TIMEOUT_MS = 6 * 60_000;
58
+ /**
59
+ * Argv for `git clone`, excluding the askpass environment.
60
+ * Positive `depth` is a shallow tip clone. Commit SHAs ignore `depth`.
61
+ */
62
+ export function gitCloneArgs({ cloneUrl, destDir, ref, depth, }) {
63
+ const shaRef = ref && looksLikeCommitSha(ref) ? ref : undefined;
64
+ const branchRef = ref && !shaRef ? ref : undefined;
65
+ const args = [
66
+ '-c',
67
+ `http.lowSpeedLimit=${GIT_LOW_SPEED_LIMIT_BYTES}`,
68
+ '-c',
69
+ `http.lowSpeedTime=${GIT_LOW_SPEED_TIME_SEC}`,
70
+ 'clone',
71
+ ];
72
+ if (depth != null && depth > 0 && !shaRef) {
73
+ args.push('--depth', String(depth));
74
+ }
75
+ if (branchRef)
76
+ args.push('--branch', branchRef);
77
+ args.push(cloneUrl, destDir);
78
+ return shaRef ? { args, shaRef } : { args };
79
+ }
58
80
  /** First non-flag arg (the git subcommand), skipping `-c <value>` config pairs. */
59
81
  function gitVerb(args) {
60
82
  for (let i = 0; i < args.length; i++) {
@@ -142,8 +164,11 @@ export async function withGitAskpass({ pat, fn, }) {
142
164
  /**
143
165
  * Clone with a clean URL + PAT via askpass. Branch/tag refs use `--branch`;
144
166
  * commit SHAs clone default HEAD then `git checkout <sha>`.
167
+ * `depth` (repos clone passes 1) fetches only that many commits — a full
168
+ * history walk against git-service reads every object from object storage
169
+ * before the pack starts, which stalls large repos.
145
170
  */
146
- export async function runGitClone({ cloneUrl, destDir, ref, pat, timeoutMs, }) {
171
+ export async function runGitClone({ cloneUrl, destDir, ref, depth, pat, timeoutMs, }) {
147
172
  let cleanUrl = cloneUrl;
148
173
  try {
149
174
  const parsed = new URL(cloneUrl);
@@ -156,23 +181,15 @@ export async function runGitClone({ cloneUrl, destDir, ref, pat, timeoutMs, }) {
156
181
  catch {
157
182
  // leave as-is; git will fail loudly
158
183
  }
159
- // Global `-c` config must precede the subcommand. Low-speed settings bound
160
- // the fetch so a stalled transport aborts fast rather than wedging the deploy.
161
- const lowSpeed = [
162
- '-c',
163
- `http.lowSpeedLimit=${GIT_LOW_SPEED_LIMIT_BYTES}`,
164
- '-c',
165
- `http.lowSpeedTime=${GIT_LOW_SPEED_TIME_SEC}`,
166
- ];
167
184
  await withGitAskpass({
168
185
  pat,
169
186
  fn: async (env) => {
170
- const args = [...lowSpeed, 'clone'];
171
- const shaRef = ref && looksLikeCommitSha(ref) ? ref : undefined;
172
- const branchRef = ref && !shaRef ? ref : undefined;
173
- if (branchRef)
174
- args.push('--branch', branchRef);
175
- args.push(cleanUrl, destDir);
187
+ const { args, shaRef } = gitCloneArgs({
188
+ cloneUrl: cleanUrl,
189
+ destDir,
190
+ ...(ref ? { ref } : {}),
191
+ ...(depth != null ? { depth } : {}),
192
+ });
176
193
  try {
177
194
  await spawnGit({ args, env, ...(timeoutMs ? { timeoutMs } : {}) });
178
195
  }
@@ -0,0 +1,217 @@
1
+ import { z } from 'zod';
2
+ /** Approved major/minor profile. Builds additionally record exact patch versions. */
3
+ export declare const ARTIFACT_NEXT_RUNTIME: Readonly<{
4
+ sdk: "sequence";
5
+ node: 24;
6
+ react: 19;
7
+ next: "16.3";
8
+ }>;
9
+ /** Admission policy for new development sessions and builds; retained releases keep their recorded toolchain. */
10
+ export declare const ARTIFACT_NEXT_APPROVED_PACKAGES: Readonly<{
11
+ next: "16.3.4";
12
+ react: "19.2.6";
13
+ 'react-dom': "19.2.6";
14
+ }>;
15
+ export declare const ARTIFACT_NEXT_RESOURCE_DEFAULTS: {
16
+ cpu: number;
17
+ memory_mb: number;
18
+ concurrency: number;
19
+ min_instances: number;
20
+ max_instances: number;
21
+ timeout_seconds: number;
22
+ };
23
+ export declare const artifactNextServerCapabilitiesSchema: z.ZodPrefault<z.ZodObject<{
24
+ storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
25
+ viewer: "viewer";
26
+ editor: "editor";
27
+ admin: "admin";
28
+ }>>>;
29
+ data: z.ZodPrefault<z.ZodObject<{
30
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
31
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
32
+ }, z.core.$strict>>;
33
+ api: z.ZodPrefault<z.ZodObject<{
34
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
35
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
36
+ }, z.core.$strict>>;
37
+ functions: z.ZodPrefault<z.ZodObject<{
38
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
39
+ }, z.core.$strict>>;
40
+ }, z.core.$strict>>;
41
+ export type ArtifactNextServerCapabilities = z.infer<typeof artifactNextServerCapabilitiesSchema>;
42
+ export declare const artifactNextResourceLimitsSchema: z.ZodPrefault<z.ZodObject<{
43
+ cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
44
+ memory_mb: z.ZodDefault<z.ZodNumber>;
45
+ concurrency: z.ZodDefault<z.ZodNumber>;
46
+ min_instances: z.ZodDefault<z.ZodNumber>;
47
+ max_instances: z.ZodDefault<z.ZodNumber>;
48
+ timeout_seconds: z.ZodDefault<z.ZodNumber>;
49
+ }, z.core.$strict>>;
50
+ export declare const artifactNextSecretNameSchema: z.ZodString;
51
+ export declare const artifactNextSecretBindingSchema: z.ZodObject<{
52
+ name: z.ZodString;
53
+ secret_id: z.ZodUUID;
54
+ version: z.ZodNumber;
55
+ }, z.core.$strict>;
56
+ /** Pinned public assets fetched by the trusted preparation stage before offline compilation. */
57
+ export declare const artifactNextPublicFontSchema: z.ZodObject<{
58
+ path: z.ZodString;
59
+ url: z.ZodString;
60
+ sha256: z.ZodString;
61
+ }, z.core.$strict>;
62
+ export type ArtifactNextPublicFont = z.infer<typeof artifactNextPublicFontSchema>;
63
+ export declare const artifactNextBuildConfigSchema: z.ZodPrefault<z.ZodObject<{
64
+ public_env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
65
+ public_fonts: z.ZodDefault<z.ZodArray<z.ZodObject<{
66
+ path: z.ZodString;
67
+ url: z.ZodString;
68
+ sha256: z.ZodString;
69
+ }, z.core.$strict>>>;
70
+ }, z.core.$strict>>;
71
+ export declare const artifactNextServerSchema: z.ZodPrefault<z.ZodObject<{
72
+ capabilities: z.ZodPrefault<z.ZodObject<{
73
+ storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
74
+ viewer: "viewer";
75
+ editor: "editor";
76
+ admin: "admin";
77
+ }>>>;
78
+ data: z.ZodPrefault<z.ZodObject<{
79
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
80
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
81
+ }, z.core.$strict>>;
82
+ api: z.ZodPrefault<z.ZodObject<{
83
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
84
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
85
+ }, z.core.$strict>>;
86
+ functions: z.ZodPrefault<z.ZodObject<{
87
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
88
+ }, z.core.$strict>>;
89
+ }, z.core.$strict>>;
90
+ service_account: z.ZodOptional<z.ZodString>;
91
+ secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
92
+ name: z.ZodString;
93
+ secret_id: z.ZodUUID;
94
+ version: z.ZodNumber;
95
+ }, z.core.$strict>>>;
96
+ egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
97
+ resources: z.ZodPrefault<z.ZodObject<{
98
+ cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
99
+ memory_mb: z.ZodDefault<z.ZodNumber>;
100
+ concurrency: z.ZodDefault<z.ZodNumber>;
101
+ min_instances: z.ZodDefault<z.ZodNumber>;
102
+ max_instances: z.ZodDefault<z.ZodNumber>;
103
+ timeout_seconds: z.ZodDefault<z.ZodNumber>;
104
+ }, z.core.$strict>>;
105
+ cache: z.ZodOptional<z.ZodObject<{
106
+ capacity_gb: z.ZodNumber;
107
+ }, z.core.$strict>>;
108
+ }, z.core.$strict>>;
109
+ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
110
+ bundle: z.ZodObject<{
111
+ name: z.ZodString;
112
+ schema_version: z.ZodLiteral<2>;
113
+ }, z.core.$strict>;
114
+ artifact: z.ZodObject<{
115
+ project_id: z.ZodString;
116
+ title: z.ZodString;
117
+ description: z.ZodOptional<z.ZodString>;
118
+ type: z.ZodLiteral<"nextjs">;
119
+ app_root: z.ZodDefault<z.ZodString>;
120
+ }, z.core.$strict>;
121
+ runtime: z.ZodPrefault<z.ZodObject<{
122
+ sdk: z.ZodDefault<z.ZodLiteral<"sequence">>;
123
+ node: z.ZodDefault<z.ZodLiteral<24>>;
124
+ react: z.ZodDefault<z.ZodLiteral<19>>;
125
+ next: z.ZodDefault<z.ZodLiteral<"16.3">>;
126
+ }, z.core.$strict>>;
127
+ capabilities: z.ZodPrefault<z.ZodObject<{
128
+ allow_downloads: z.ZodDefault<z.ZodBoolean>;
129
+ allow_popups: z.ZodDefault<z.ZodBoolean>;
130
+ roles: z.ZodOptional<z.ZodObject<{
131
+ definitions: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
132
+ description: z.ZodOptional<z.ZodString>;
133
+ default: z.ZodDefault<z.ZodBoolean>;
134
+ inherits: z.ZodDefault<z.ZodArray<z.ZodString>>;
135
+ grants: z.ZodDefault<z.ZodArray<z.ZodString>>;
136
+ }, z.core.$strict>>>;
137
+ capabilities: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
138
+ description: z.ZodOptional<z.ZodString>;
139
+ }, z.core.$strict>>>;
140
+ }, z.core.$strict>>;
141
+ uses: z.ZodDefault<z.ZodArray<z.ZodString>>;
142
+ storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
143
+ viewer: "viewer";
144
+ editor: "editor";
145
+ admin: "admin";
146
+ }>>>;
147
+ data: z.ZodPrefault<z.ZodObject<{
148
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
149
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
150
+ }, z.core.$strict>>;
151
+ api: z.ZodPrefault<z.ZodObject<{
152
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
153
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
154
+ }, z.core.$strict>>;
155
+ functions: z.ZodPrefault<z.ZodObject<{
156
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
157
+ }, z.core.$strict>>;
158
+ }, z.core.$strict>>;
159
+ build: z.ZodPrefault<z.ZodObject<{
160
+ public_env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
161
+ public_fonts: z.ZodDefault<z.ZodArray<z.ZodObject<{
162
+ path: z.ZodString;
163
+ url: z.ZodString;
164
+ sha256: z.ZodString;
165
+ }, z.core.$strict>>>;
166
+ }, z.core.$strict>>;
167
+ server: z.ZodPrefault<z.ZodObject<{
168
+ capabilities: z.ZodPrefault<z.ZodObject<{
169
+ storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
170
+ viewer: "viewer";
171
+ editor: "editor";
172
+ admin: "admin";
173
+ }>>>;
174
+ data: z.ZodPrefault<z.ZodObject<{
175
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
176
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
177
+ }, z.core.$strict>>;
178
+ api: z.ZodPrefault<z.ZodObject<{
179
+ read: z.ZodDefault<z.ZodArray<z.ZodString>>;
180
+ write: z.ZodDefault<z.ZodArray<z.ZodString>>;
181
+ }, z.core.$strict>>;
182
+ functions: z.ZodPrefault<z.ZodObject<{
183
+ invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
184
+ }, z.core.$strict>>;
185
+ }, z.core.$strict>>;
186
+ service_account: z.ZodOptional<z.ZodString>;
187
+ secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
188
+ name: z.ZodString;
189
+ secret_id: z.ZodUUID;
190
+ version: z.ZodNumber;
191
+ }, z.core.$strict>>>;
192
+ egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
193
+ resources: z.ZodPrefault<z.ZodObject<{
194
+ cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
195
+ memory_mb: z.ZodDefault<z.ZodNumber>;
196
+ concurrency: z.ZodDefault<z.ZodNumber>;
197
+ min_instances: z.ZodDefault<z.ZodNumber>;
198
+ max_instances: z.ZodDefault<z.ZodNumber>;
199
+ timeout_seconds: z.ZodDefault<z.ZodNumber>;
200
+ }, z.core.$strict>>;
201
+ cache: z.ZodOptional<z.ZodObject<{
202
+ capacity_gb: z.ZodNumber;
203
+ }, z.core.$strict>>;
204
+ }, z.core.$strict>>;
205
+ targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
206
+ url: z.ZodString;
207
+ }, z.core.$strict>>>;
208
+ sidebarIcon: z.ZodOptional<z.ZodString>;
209
+ sidebarBadge: z.ZodOptional<z.ZodObject<{
210
+ endpoint: z.ZodString;
211
+ tone: z.ZodOptional<z.ZodEnum<{
212
+ alert: "alert";
213
+ muted: "muted";
214
+ }>>;
215
+ }, z.core.$strict>>;
216
+ }, z.core.$strict>;
217
+ export type ArtifactStudioNextManifest = z.infer<typeof artifactStudioNextManifestSchema>;