@agentuity/cli 1.0.20 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmd/build/vite/static-renderer.d.ts +27 -0
- package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -0
- package/dist/cmd/build/vite/static-renderer.js +182 -0
- package/dist/cmd/build/vite/static-renderer.js.map +1 -0
- package/dist/cmd/build/vite/vite-builder.d.ts +5 -0
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +16 -0
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite-bundler.js +3 -0
- package/dist/cmd/build/vite-bundler.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +1 -1
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/project/add/domain.d.ts.map +1 -1
- package/dist/cmd/project/add/domain.js +6 -4
- package/dist/cmd/project/add/domain.js.map +1 -1
- package/dist/cmd/project/domain/check.d.ts.map +1 -1
- package/dist/cmd/project/domain/check.js +10 -5
- package/dist/cmd/project/domain/check.js.map +1 -1
- package/dist/cmd/project/reconcile.d.ts.map +1 -1
- package/dist/cmd/project/reconcile.js +19 -7
- package/dist/cmd/project/reconcile.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +2 -1
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/domain.d.ts +3 -2
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +90 -21
- package/dist/domain.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/build/vite/static-renderer.ts +236 -0
- package/src/cmd/build/vite/vite-builder.ts +18 -0
- package/src/cmd/build/vite-bundler.ts +7 -0
- package/src/cmd/cloud/deploy.ts +1 -0
- package/src/cmd/project/add/domain.ts +10 -4
- package/src/cmd/project/domain/check.ts +16 -5
- package/src/cmd/project/reconcile.ts +31 -13
- package/src/cmd/project/template-flow.ts +2 -1
- package/src/domain.ts +99 -21
- package/src/types.ts +8 -0
package/src/domain.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { Config } from './types';
|
|
2
2
|
import { StructuredError } from '@agentuity/core';
|
|
3
|
+
import { getIONHost } from './config';
|
|
3
4
|
import * as tui from './tui';
|
|
4
5
|
|
|
5
6
|
interface BaseDNSResult {
|
|
6
7
|
domain: string;
|
|
7
8
|
target: string;
|
|
8
9
|
recordType: string;
|
|
10
|
+
aRecordTarget?: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
interface DNSSuccess extends BaseDNSResult {
|
|
@@ -68,23 +70,29 @@ interface CFRecord {
|
|
|
68
70
|
}[];
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
async function
|
|
73
|
+
async function fetchDNSRecords(name: string, type: string): Promise<string[]> {
|
|
72
74
|
const params = new URLSearchParams();
|
|
73
75
|
params.set('name', name);
|
|
74
76
|
params.set('type', type);
|
|
77
|
+
params.set('_', Date.now().toString());
|
|
75
78
|
const res = await fetch(`https://cloudflare-dns.com/dns-query?${params.toString()}`, {
|
|
76
79
|
headers: {
|
|
77
80
|
Accept: 'application/dns-json',
|
|
78
81
|
},
|
|
82
|
+
// @ts-expect-error - cache is supported by Bun's fetch at runtime but missing from type definitions
|
|
83
|
+
cache: 'no-store',
|
|
79
84
|
});
|
|
80
85
|
if (res.ok) {
|
|
81
86
|
const result = (await res.json()) as CFRecord;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return firstAnswer.data.replace(/\.$/, ''); // DNS records end with . so we remove that
|
|
85
|
-
}
|
|
87
|
+
// DNS records end with . so we remove that
|
|
88
|
+
return (result?.Answer ?? []).map((a) => a.data.replace(/\.$/, ''));
|
|
86
89
|
}
|
|
87
|
-
return
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function fetchDNSRecord(name: string, type: string): Promise<string | null> {
|
|
94
|
+
const records = await fetchDNSRecords(name, type);
|
|
95
|
+
return records[0] ?? null;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
const LOCAL_DNS = 'agentuity.io';
|
|
@@ -102,12 +110,24 @@ const PRODUCTION_DNS = 'agentuity.run';
|
|
|
102
110
|
export async function checkCustomDomainForDNS(
|
|
103
111
|
projectId: string,
|
|
104
112
|
domains: string[],
|
|
113
|
+
region: string,
|
|
105
114
|
config?: Config | null
|
|
106
115
|
): Promise<DNSResult[]> {
|
|
107
116
|
const suffix = config?.overrides?.api_url?.includes('agentuity.io') ? LOCAL_DNS : PRODUCTION_DNS;
|
|
108
117
|
const id = Bun.hash.xxHash64(projectId).toString(16).padStart(16, '0');
|
|
109
118
|
const proxy = `p${id}.${suffix}`;
|
|
110
119
|
|
|
120
|
+
// Resolve the ION host A record(s) so we can validate A records
|
|
121
|
+
// and show the user what IP to point their A record to
|
|
122
|
+
const ionHost = getIONHost(config ?? null, region);
|
|
123
|
+
let ionIPs: string[] = [];
|
|
124
|
+
try {
|
|
125
|
+
ionIPs = await fetchDNSRecords(ionHost, 'A');
|
|
126
|
+
} catch {
|
|
127
|
+
// If we can't resolve the ION host, A record validation will be skipped
|
|
128
|
+
}
|
|
129
|
+
const aRecordTarget = ionIPs[0] ?? undefined;
|
|
130
|
+
|
|
111
131
|
return Promise.all(
|
|
112
132
|
domains.map(async (domain) => {
|
|
113
133
|
// Detect if user passed a URL instead of a domain name
|
|
@@ -117,6 +137,7 @@ export async function checkCustomDomainForDNS(
|
|
|
117
137
|
return {
|
|
118
138
|
domain,
|
|
119
139
|
target: proxy,
|
|
140
|
+
aRecordTarget,
|
|
120
141
|
recordType: 'CNAME',
|
|
121
142
|
success: false,
|
|
122
143
|
error: `Invalid domain format: "${domain}" appears to be a URL. Use just the domain name: "${url.hostname}"`,
|
|
@@ -125,6 +146,7 @@ export async function checkCustomDomainForDNS(
|
|
|
125
146
|
return {
|
|
126
147
|
domain,
|
|
127
148
|
target: proxy,
|
|
149
|
+
aRecordTarget,
|
|
128
150
|
recordType: 'CNAME',
|
|
129
151
|
success: false,
|
|
130
152
|
error: `Invalid domain format: "${domain}" appears to be a URL. Use just the domain name without the protocol (e.g., "example.com" not "https://example.com")`,
|
|
@@ -132,6 +154,7 @@ export async function checkCustomDomainForDNS(
|
|
|
132
154
|
}
|
|
133
155
|
}
|
|
134
156
|
|
|
157
|
+
// Step 1: Check CNAME record
|
|
135
158
|
try {
|
|
136
159
|
let timeoutId: Timer | undefined;
|
|
137
160
|
|
|
@@ -153,6 +176,7 @@ export async function checkCustomDomainForDNS(
|
|
|
153
176
|
return {
|
|
154
177
|
domain,
|
|
155
178
|
target: proxy,
|
|
179
|
+
aRecordTarget,
|
|
156
180
|
recordType: 'CNAME',
|
|
157
181
|
success: true,
|
|
158
182
|
} as DNSSuccess;
|
|
@@ -160,9 +184,10 @@ export async function checkCustomDomainForDNS(
|
|
|
160
184
|
return {
|
|
161
185
|
domain,
|
|
162
186
|
target: proxy,
|
|
187
|
+
aRecordTarget,
|
|
163
188
|
recordType: 'CNAME',
|
|
164
189
|
success: false,
|
|
165
|
-
misconfigured: `CNAME record
|
|
190
|
+
misconfigured: `CNAME record points to ${result}`,
|
|
166
191
|
} as DNSMisconfigured;
|
|
167
192
|
}
|
|
168
193
|
} catch (ex) {
|
|
@@ -171,6 +196,7 @@ export async function checkCustomDomainForDNS(
|
|
|
171
196
|
return {
|
|
172
197
|
domain,
|
|
173
198
|
target: proxy,
|
|
199
|
+
aRecordTarget,
|
|
174
200
|
recordType: 'CNAME',
|
|
175
201
|
success: false,
|
|
176
202
|
error: `DNS lookup timed out after 5 seconds. Please check your DNS configuration.`,
|
|
@@ -186,16 +212,63 @@ export async function checkCustomDomainForDNS(
|
|
|
186
212
|
return {
|
|
187
213
|
domain,
|
|
188
214
|
target: proxy,
|
|
215
|
+
aRecordTarget,
|
|
189
216
|
recordType: 'CNAME',
|
|
190
217
|
success: false,
|
|
191
218
|
error: errMsg,
|
|
192
219
|
} as DNSError;
|
|
193
220
|
}
|
|
221
|
+
// ENOTFOUND: no CNAME record exists, fall through to A record check
|
|
194
222
|
}
|
|
223
|
+
|
|
224
|
+
// Step 2: Check A record (supports apex domains and ALIAS/ANAME/CNAME-flattening)
|
|
225
|
+
if (ionIPs.length > 0) {
|
|
226
|
+
try {
|
|
227
|
+
let aTimeoutId: Timer | undefined;
|
|
228
|
+
|
|
229
|
+
const aTimeoutPromise = new Promise<never>((_, reject) => {
|
|
230
|
+
aTimeoutId = setTimeout(() => {
|
|
231
|
+
reject(new DNSTimeoutError());
|
|
232
|
+
}, timeoutMs);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const domainARecords = await Promise.race([
|
|
236
|
+
fetchDNSRecords(domain, 'A'),
|
|
237
|
+
aTimeoutPromise,
|
|
238
|
+
]).finally(() => {
|
|
239
|
+
if (aTimeoutId) clearTimeout(aTimeoutId);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (domainARecords.length > 0) {
|
|
243
|
+
const matching = domainARecords.some((a) => ionIPs.includes(a));
|
|
244
|
+
if (matching) {
|
|
245
|
+
return {
|
|
246
|
+
domain,
|
|
247
|
+
target: proxy,
|
|
248
|
+
aRecordTarget,
|
|
249
|
+
recordType: 'A',
|
|
250
|
+
success: true,
|
|
251
|
+
} as DNSSuccess;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
domain,
|
|
255
|
+
target: proxy,
|
|
256
|
+
aRecordTarget,
|
|
257
|
+
recordType: 'A',
|
|
258
|
+
success: false,
|
|
259
|
+
misconfigured: `A record points to ${domainARecords[0]}, expected ${aRecordTarget}`,
|
|
260
|
+
} as DNSMisconfigured;
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
// A record check failed, fall through to missing
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
195
267
|
return {
|
|
196
268
|
domain,
|
|
197
269
|
success: false,
|
|
198
270
|
target: proxy,
|
|
271
|
+
aRecordTarget,
|
|
199
272
|
recordType: 'CNAME',
|
|
200
273
|
pending: false,
|
|
201
274
|
} as DNSMissing;
|
|
@@ -206,27 +279,28 @@ export async function checkCustomDomainForDNS(
|
|
|
206
279
|
export async function promptForDNS(
|
|
207
280
|
projectId: string,
|
|
208
281
|
domains: string[],
|
|
282
|
+
region: string,
|
|
209
283
|
config?: Config,
|
|
210
284
|
resumeFn?: () => () => void
|
|
211
285
|
) {
|
|
212
286
|
let paused = false;
|
|
213
287
|
let resume: (() => void) | undefined;
|
|
214
288
|
for (;;) {
|
|
215
|
-
const result = await checkCustomDomainForDNS(projectId, domains, config);
|
|
289
|
+
const result = await checkCustomDomainForDNS(projectId, domains, region, config);
|
|
216
290
|
const failed = result.filter((x): x is DNSFailed => !isSuccess(x));
|
|
217
291
|
if (failed.length) {
|
|
218
292
|
const records: {
|
|
219
293
|
domain: string;
|
|
220
|
-
|
|
221
|
-
|
|
294
|
+
cnameTarget: string;
|
|
295
|
+
aRecordTarget?: string;
|
|
222
296
|
status: string;
|
|
223
297
|
}[] = [];
|
|
224
298
|
result.forEach((r) => {
|
|
225
299
|
if (isSuccess(r)) {
|
|
226
300
|
records.push({
|
|
227
301
|
domain: r.domain,
|
|
228
|
-
|
|
229
|
-
|
|
302
|
+
cnameTarget: r.target,
|
|
303
|
+
aRecordTarget: r.aRecordTarget,
|
|
230
304
|
status: tui.colorSuccess(`${tui.ICONS.success} Configured`),
|
|
231
305
|
});
|
|
232
306
|
}
|
|
@@ -245,22 +319,22 @@ export async function promptForDNS(
|
|
|
245
319
|
} else if (isMisconfigured(r)) {
|
|
246
320
|
records.push({
|
|
247
321
|
domain: r.domain,
|
|
248
|
-
|
|
249
|
-
|
|
322
|
+
cnameTarget: r.target,
|
|
323
|
+
aRecordTarget: r.aRecordTarget,
|
|
250
324
|
status: tui.colorWarning(`${tui.ICONS.error} ${r.misconfigured}`),
|
|
251
325
|
});
|
|
252
326
|
} else if (isPending(r)) {
|
|
253
327
|
records.push({
|
|
254
328
|
domain: r.domain,
|
|
255
|
-
|
|
256
|
-
|
|
329
|
+
cnameTarget: r.target,
|
|
330
|
+
aRecordTarget: r.aRecordTarget,
|
|
257
331
|
status: tui.colorWarning('⌛️ Pending'),
|
|
258
332
|
});
|
|
259
333
|
} else if (isMissing(r)) {
|
|
260
334
|
records.push({
|
|
261
335
|
domain: r.domain,
|
|
262
|
-
|
|
263
|
-
|
|
336
|
+
cnameTarget: r.target,
|
|
337
|
+
aRecordTarget: r.aRecordTarget,
|
|
264
338
|
status: tui.colorError(`${tui.ICONS.error} Missing`),
|
|
265
339
|
});
|
|
266
340
|
}
|
|
@@ -270,11 +344,15 @@ export async function promptForDNS(
|
|
|
270
344
|
for (const record of records) {
|
|
271
345
|
console.log();
|
|
272
346
|
console.log(`${tui.colorInfo('Domain:')} ${tui.colorPrimary(record.domain)}`);
|
|
273
|
-
console.log(`${tui.colorInfo('
|
|
274
|
-
|
|
347
|
+
console.log(`${tui.colorInfo('CNAME:')} ${tui.colorPrimary(record.cnameTarget)}`);
|
|
348
|
+
if (record.aRecordTarget) {
|
|
349
|
+
console.log(
|
|
350
|
+
`${tui.colorInfo('A:')} ${tui.colorPrimary(record.aRecordTarget)}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
275
353
|
console.log(`${tui.colorInfo('Status:')} ${tui.colorPrimary(record.status)}`);
|
|
276
354
|
console.log();
|
|
277
|
-
linesShown += 6;
|
|
355
|
+
linesShown += record.aRecordTarget ? 6 : 5;
|
|
278
356
|
}
|
|
279
357
|
|
|
280
358
|
// await tui.waitForAnyKey('Press any key to check again or ctrl+c to cancel...');
|
package/src/types.ts
CHANGED
|
@@ -215,6 +215,14 @@ export interface AnalyticsConfig {
|
|
|
215
215
|
* Agentuity project configuration (declarative)
|
|
216
216
|
*/
|
|
217
217
|
export interface AgentuityConfig {
|
|
218
|
+
/**
|
|
219
|
+
* Rendering mode for the web frontend.
|
|
220
|
+
* - 'spa' (default): Single-page application with client-side routing
|
|
221
|
+
* - 'static': Pre-renders all routes to static HTML at build time (SSG).
|
|
222
|
+
* Requires src/web/entry-server.tsx exporting render(url) and getStaticPaths()
|
|
223
|
+
*/
|
|
224
|
+
render?: 'spa' | 'static';
|
|
225
|
+
|
|
218
226
|
/**
|
|
219
227
|
* Workbench configuration
|
|
220
228
|
*/
|