@myapihq/cli 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/container.d.ts +1 -0
- package/dist/commands/container.js +41 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.js +154 -0
- package/dist/completion.js +2 -2
- package/dist/exposes.test.js +1 -0
- package/dist/index.js +7 -0
- package/dist/sdk-container.test.js +35 -1
- package/dist/skills/my-domain-api/SKILL.md +2 -0
- package/package.json +2 -2
|
@@ -13,4 +13,5 @@ export declare function get(id: string, flags: Flags): Promise<void>;
|
|
|
13
13
|
export declare function del(id: string, flags: Flags): Promise<void>;
|
|
14
14
|
export declare function deploy(id: string, image: string, flags: Flags): Promise<void>;
|
|
15
15
|
export declare function logs(id: string, flags: Flags): Promise<void>;
|
|
16
|
+
export declare function domain(id: string, domainArg: string | undefined, flags: Flags): Promise<void>;
|
|
16
17
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -10,6 +10,8 @@ export const EXPOSES = [
|
|
|
10
10
|
'DELETE /container/orgs/{org_id}/containers/{id}',
|
|
11
11
|
'POST /container/orgs/{org_id}/containers/{id}/deploy',
|
|
12
12
|
'GET /container/orgs/{org_id}/containers/{id}/logs',
|
|
13
|
+
'POST /container/orgs/{org_id}/containers/{id}/domain',
|
|
14
|
+
'DELETE /container/orgs/{org_id}/containers/{id}/domain',
|
|
13
15
|
];
|
|
14
16
|
export const SCHEMA = {
|
|
15
17
|
name: 'string',
|
|
@@ -22,6 +24,7 @@ export const SCHEMA = {
|
|
|
22
24
|
port: 'number',
|
|
23
25
|
env: 'string',
|
|
24
26
|
tail: 'number',
|
|
27
|
+
remove: 'boolean',
|
|
25
28
|
};
|
|
26
29
|
const CONTAINER_TYPES = ['service', 'worker', 'job'];
|
|
27
30
|
// Mirrors validateName in myapi-hq/internal/routes/container/crud.go —
|
|
@@ -146,6 +149,8 @@ export async function get(id, flags) {
|
|
|
146
149
|
if (c.port)
|
|
147
150
|
info(`Port: ${c.port}`);
|
|
148
151
|
info(`URL: ${c.url || '(not deployed)'}`);
|
|
152
|
+
if (c.custom_domain)
|
|
153
|
+
info(`Custom domain: ${c.custom_domain}`);
|
|
149
154
|
info(`Created: ${c.created_at}`);
|
|
150
155
|
info(`Updated: ${c.updated_at}`);
|
|
151
156
|
}
|
|
@@ -198,6 +203,31 @@ export async function logs(id, flags) {
|
|
|
198
203
|
info(`${e.timestamp} ${(e.severity || '').padEnd(8)} ${e.text}`);
|
|
199
204
|
}
|
|
200
205
|
}
|
|
206
|
+
// domain binds (or, with --remove, unbinds) a custom domain on a deployed
|
|
207
|
+
// container. The parent domain must be MyAPI-managed.
|
|
208
|
+
export async function domain(id, domainArg, flags) {
|
|
209
|
+
const config = requireConfig();
|
|
210
|
+
const orgId = requireOrg(flags, config, 'myapi container domain <id> <domain> [--org <id>]');
|
|
211
|
+
if (!id)
|
|
212
|
+
error('Missing id.\nUsage: myapi container domain <id> <domain> (or --remove to unbind)');
|
|
213
|
+
if (flags.remove) {
|
|
214
|
+
await sdkContainer.unbindDomain(config.api_key, orgId, id);
|
|
215
|
+
success(`Removed custom domain from container ${id}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!domainArg) {
|
|
219
|
+
error('Missing <domain>.\nUsage: myapi container domain <id> <domain>\n or: myapi container domain <id> --remove');
|
|
220
|
+
}
|
|
221
|
+
const binding = await sdkContainer.bindDomain(config.api_key, orgId, id, domainArg);
|
|
222
|
+
if (flags.json) {
|
|
223
|
+
printJson(binding);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
success(`Custom domain bound: ${binding.custom_domain}`);
|
|
227
|
+
info(`Container: ${binding.container_id}`);
|
|
228
|
+
info(`Origin: ${binding.origin}`);
|
|
229
|
+
info(`Status: ${binding.status}`);
|
|
230
|
+
}
|
|
201
231
|
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
|
202
232
|
const SUBCOMMAND_USAGE = {
|
|
203
233
|
'create': `myapi container create --name <name> [--type service|worker|job] [--cron <expr>]
|
|
@@ -229,6 +259,15 @@ Example:
|
|
|
229
259
|
|
|
230
260
|
Recent Cloud Run runtime logs, newest first. --tail caps the count
|
|
231
261
|
(default 100, max 1000).`,
|
|
262
|
+
'domain': `myapi container domain <id> <domain> [--org <id>] [--json]
|
|
263
|
+
myapi container domain <id> --remove [--org <id>]
|
|
264
|
+
|
|
265
|
+
Binds a custom domain to a deployed container, served over HTTPS via
|
|
266
|
+
Cloudflare. The domain's MyAPI-managed parent domain must already be
|
|
267
|
+
registered. --remove unbinds it.
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
myapi container domain <id> app.synthesisdaily.com`,
|
|
232
271
|
'delete': 'myapi container delete <id> [--org <id>]',
|
|
233
272
|
};
|
|
234
273
|
export async function run(subcommand, args, flags) {
|
|
@@ -245,6 +284,7 @@ Subcommands:
|
|
|
245
284
|
list List containers in your org
|
|
246
285
|
get <id> Inspect a container
|
|
247
286
|
logs <id> Show recent runtime logs (--tail <n>)
|
|
287
|
+
domain <id> <domain> Bind a custom domain (--remove to unbind)
|
|
248
288
|
delete <id> Soft-delete and revoke its scoped API key
|
|
249
289
|
|
|
250
290
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
@@ -264,6 +304,7 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
|
264
304
|
case 'list': return list(flags);
|
|
265
305
|
case 'get': return get(args[0], flags);
|
|
266
306
|
case 'logs': return logs(args[0], flags);
|
|
307
|
+
case 'domain': return domain(args[0], args[1], flags);
|
|
267
308
|
case 'delete': return del(args[0], flags);
|
|
268
309
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi container --help" for a list of valid subcommands.`);
|
|
269
310
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FlagSchema } from '../flags.js';
|
|
2
|
+
import { type Flags } from '../helpers.js';
|
|
3
|
+
import type { Exposes } from '../exposes.js';
|
|
4
|
+
export declare const EXPOSES: Exposes;
|
|
5
|
+
export declare const SCHEMA: FlagSchema;
|
|
6
|
+
export declare function run(_subcommand: string | undefined, _args: string[], flags: Flags): Promise<void>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// `myapi doctor` — thin client over the backend's GET /hq/orgs/{org_id}/doctor.
|
|
2
|
+
//
|
|
3
|
+
// The backend computes the heavy reference-integrity / orphan / activity
|
|
4
|
+
// checks across slots and returns a structured report. The CLI's job is:
|
|
5
|
+
// 1. fetch the report,
|
|
6
|
+
// 2. augment with *customer-perspective* probes the backend structurally
|
|
7
|
+
// can't run (today: DNS resolution from the user's network),
|
|
8
|
+
// 3. render with section grouping, color, and exit codes for CI.
|
|
9
|
+
import { promises as dns } from 'node:dns';
|
|
10
|
+
import { hq as sdkHq } from '@myapihq/sdk';
|
|
11
|
+
import { requireConfig } from '../config.js';
|
|
12
|
+
import { info, error, printJson } from '../output.js';
|
|
13
|
+
import { requireOrg } from '../helpers.js';
|
|
14
|
+
export const EXPOSES = [
|
|
15
|
+
'GET /hq/orgs/{org_id}/doctor',
|
|
16
|
+
];
|
|
17
|
+
export const SCHEMA = {};
|
|
18
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
19
|
+
const C = useColor ? {
|
|
20
|
+
ok: '\x1b[32m', warn: '\x1b[33m', err: '\x1b[31m',
|
|
21
|
+
dim: '\x1b[2m', bold: '\x1b[1m', reset: '\x1b[0m',
|
|
22
|
+
} : { ok: '', warn: '', err: '', dim: '', bold: '', reset: '' };
|
|
23
|
+
const MARK = {
|
|
24
|
+
ok: `${C.ok}✓${C.reset}`,
|
|
25
|
+
warn: `${C.warn}⚠${C.reset}`,
|
|
26
|
+
crit: `${C.err}✗${C.reset}`,
|
|
27
|
+
};
|
|
28
|
+
function rule(width = 60) {
|
|
29
|
+
return `${C.dim}${'─'.repeat(width)}${C.reset}`;
|
|
30
|
+
}
|
|
31
|
+
function fmtIssue(i) {
|
|
32
|
+
const head = ` ${MARK[i.severity] ?? '·'} ${i.message}`;
|
|
33
|
+
return i.hint ? `${head}\n ${C.dim}→ ${i.hint}${C.reset}` : head;
|
|
34
|
+
}
|
|
35
|
+
// Local DNS-resolution probe for every distinct domain the report names.
|
|
36
|
+
// The backend can verify domain provisioning state from its own egress;
|
|
37
|
+
// this checks whether the operator's network reaches them today — a
|
|
38
|
+
// different epistemic signal worth surfacing on top of the backend's view.
|
|
39
|
+
async function dnsProbeSection(report) {
|
|
40
|
+
const names = new Set();
|
|
41
|
+
for (const s of report.sections) {
|
|
42
|
+
for (const i of s.issues) {
|
|
43
|
+
if (i.entity?.slot === 'domain' && i.entity.name)
|
|
44
|
+
names.add(i.entity.name);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (names.size === 0)
|
|
48
|
+
return null;
|
|
49
|
+
const issues = [];
|
|
50
|
+
await Promise.all([...names].map(async (name) => {
|
|
51
|
+
try {
|
|
52
|
+
const ips = await dns.resolve4(name);
|
|
53
|
+
issues.push({
|
|
54
|
+
id: `dns_local_ok/${name}`,
|
|
55
|
+
severity: 'ok',
|
|
56
|
+
scope: `local/${name}`,
|
|
57
|
+
entity: { slot: 'domain', id: '', name },
|
|
58
|
+
category: 'network',
|
|
59
|
+
message: `${name} resolves from your network${ips.length ? ` (${ips[0]}${ips.length > 1 ? ` +${ips.length - 1}` : ''})` : ''}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
issues.push({
|
|
64
|
+
id: `dns_local_fail/${name}`,
|
|
65
|
+
severity: 'warn',
|
|
66
|
+
scope: `local/${name}`,
|
|
67
|
+
entity: { slot: 'domain', id: '', name },
|
|
68
|
+
category: 'network',
|
|
69
|
+
message: `${name} did not resolve from your network`,
|
|
70
|
+
hint: e?.code || e?.message || String(e),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}));
|
|
74
|
+
const warns = issues.filter(i => i.severity === 'warn').length;
|
|
75
|
+
return {
|
|
76
|
+
name: 'local network',
|
|
77
|
+
summary: warns ? `${warns} resolution failure${warns === 1 ? '' : 's'}` : `${issues.length} domain${issues.length === 1 ? '' : 's'} resolved`,
|
|
78
|
+
issues,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export async function run(_subcommand, _args, flags) {
|
|
82
|
+
if (flags.help) {
|
|
83
|
+
info(HELP);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const config = requireConfig();
|
|
87
|
+
const orgId = requireOrg(flags, config, 'myapi doctor [--json] [--verbose]');
|
|
88
|
+
const apiKey = config.api_key;
|
|
89
|
+
const verbose = !!flags.verbose;
|
|
90
|
+
const wantJson = !!flags.json;
|
|
91
|
+
let report;
|
|
92
|
+
try {
|
|
93
|
+
report = await sdkHq.getDoctor(apiKey, orgId);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
error(`doctor endpoint failed: ${e?.message ?? String(e)}`);
|
|
97
|
+
}
|
|
98
|
+
const localSection = await dnsProbeSection(report);
|
|
99
|
+
if (localSection)
|
|
100
|
+
report.sections.push(localSection);
|
|
101
|
+
// Re-tally totals after local augmentation.
|
|
102
|
+
const totals = { ok: 0, warn: 0, crit: 0 };
|
|
103
|
+
for (const s of report.sections)
|
|
104
|
+
for (const i of s.issues)
|
|
105
|
+
totals[i.severity]++;
|
|
106
|
+
if (wantJson) {
|
|
107
|
+
printJson({ ...report, totals });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
info(`${C.bold}Org doctor${C.reset} ${C.dim}· org ${report.org_id}${C.reset} ${C.dim}· ${report.generated_at}${C.reset}`);
|
|
111
|
+
for (const s of report.sections) {
|
|
112
|
+
info('');
|
|
113
|
+
info(`${C.bold}# ${s.name}${C.reset} ${C.dim}· ${s.summary}${C.reset}`);
|
|
114
|
+
for (const i of s.issues) {
|
|
115
|
+
if (!verbose && i.severity === 'ok')
|
|
116
|
+
continue;
|
|
117
|
+
info(fmtIssue(i));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
info('');
|
|
121
|
+
info(rule(60));
|
|
122
|
+
if (totals.crit) {
|
|
123
|
+
info(`${MARK.crit} ${totals.crit} critical, ${MARK.warn} ${totals.warn} warning${totals.warn === 1 ? '' : 's'}`);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
}
|
|
126
|
+
else if (totals.warn) {
|
|
127
|
+
info(`${MARK.warn} ${totals.warn} warning${totals.warn === 1 ? '' : 's'}, no critical issues`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
info(`${MARK.ok} ${totals.ok} check${totals.ok === 1 ? '' : 's'} passed`);
|
|
131
|
+
}
|
|
132
|
+
if (!verbose)
|
|
133
|
+
info(`${C.dim}(--verbose to show passing checks · --json for machine-readable)${C.reset}`);
|
|
134
|
+
}
|
|
135
|
+
const HELP = `Usage: myapi doctor [--verbose] [--json] [--org <id>]
|
|
136
|
+
|
|
137
|
+
Org-wide consistency check. Fetches the structured report from the backend
|
|
138
|
+
(GET /hq/orgs/{org_id}/doctor) and augments it with customer-perspective
|
|
139
|
+
probes (DNS resolution from this machine's network).
|
|
140
|
+
|
|
141
|
+
Sections returned by the backend today:
|
|
142
|
+
funnels, webhooks, workflows, domains, containers, emails, payments
|
|
143
|
+
|
|
144
|
+
Local additions:
|
|
145
|
+
network — DNS resolution from your egress for each domain mentioned.
|
|
146
|
+
|
|
147
|
+
Exit codes:
|
|
148
|
+
0 no critical issues (warnings allowed)
|
|
149
|
+
1 one or more critical issues
|
|
150
|
+
|
|
151
|
+
Options:
|
|
152
|
+
--verbose Show passing checks too.
|
|
153
|
+
--json Machine-readable output.
|
|
154
|
+
--org <id> Override the default org.`;
|
package/dist/completion.js
CHANGED
|
@@ -28,7 +28,7 @@ const BLOCK_END = `# end ${PROGRAM} completion`;
|
|
|
28
28
|
export const COMMANDS = [
|
|
29
29
|
'audience', 'auth', 'billing', 'company', 'completion', 'config', 'container',
|
|
30
30
|
'crm', 'database', 'domain', 'email', 'fn', 'funnel', 'git', 'help', 'image',
|
|
31
|
-
'install-skills', 'keys', 'llm', 'org', 'payments', 'people', 'pixel', 'queue',
|
|
31
|
+
'doctor', 'install-skills', 'keys', 'llm', 'org', 'payments', 'people', 'pixel', 'queue',
|
|
32
32
|
'setup', 'status', 'storage', 'task', 'update', 'url', 'webhook', 'whoami',
|
|
33
33
|
'workflow',
|
|
34
34
|
];
|
|
@@ -57,7 +57,7 @@ export const SUBCOMMANDS = {
|
|
|
57
57
|
config: ['view', 'set-org', 'set-funnel', 'set-domain'],
|
|
58
58
|
fn: ['create', 'deploy', 'env', 'runs', 'list', 'get', 'delete'],
|
|
59
59
|
payments: ['connect', 'status', 'charge', 'list', 'get', 'refund'],
|
|
60
|
-
container: ['create', 'deploy', 'list', 'get', 'logs', 'delete'],
|
|
60
|
+
container: ['create', 'deploy', 'list', 'get', 'logs', 'domain', 'delete'],
|
|
61
61
|
git: ['create', 'list', 'get', 'delete', 'refs', 'log', 'show', 'tree', 'blob', 'diff', 'commit', 'create-branch', 'delete-branch', 'tag', 'merge', 'repack'],
|
|
62
62
|
queue: ['create', 'list', 'get', 'enqueue', 'jobs', 'job'],
|
|
63
63
|
task: ['create', 'list', 'get', 'claim', 'extend', 'resolve', 'fail', 'cancel'],
|
package/dist/exposes.test.js
CHANGED
|
@@ -43,6 +43,7 @@ const COMMAND_MODULES = [
|
|
|
43
43
|
'./commands/git.js',
|
|
44
44
|
'./commands/queue.js',
|
|
45
45
|
'./commands/task.js',
|
|
46
|
+
'./commands/doctor.js',
|
|
46
47
|
];
|
|
47
48
|
const ENDPOINT_PATTERN = /^(GET|POST|PATCH|PUT|DELETE) \/[A-Za-z0-9_\-./{}]*$/;
|
|
48
49
|
describe('every CLI command exports a typed EXPOSES array (S-101)', () => {
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ import * as containerCmd from './commands/container.js';
|
|
|
35
35
|
import * as gitCmd from './commands/git.js';
|
|
36
36
|
import * as queueCmd from './commands/queue.js';
|
|
37
37
|
import * as taskCmd from './commands/task.js';
|
|
38
|
+
import * as doctorCmd from './commands/doctor.js';
|
|
38
39
|
// Each command file declares the value flags it understands. We union them
|
|
39
40
|
// into a single schema for the upfront parse, so adding a new value flag in
|
|
40
41
|
// one command means editing one file (its SCHEMA), not a global allowlist.
|
|
@@ -66,6 +67,7 @@ const COMBINED_SCHEMA = {
|
|
|
66
67
|
...gitCmd.SCHEMA,
|
|
67
68
|
...queueCmd.SCHEMA,
|
|
68
69
|
...taskCmd.SCHEMA,
|
|
70
|
+
...doctorCmd.SCHEMA,
|
|
69
71
|
// Top-level flags
|
|
70
72
|
version: 'boolean',
|
|
71
73
|
V: 'boolean',
|
|
@@ -234,6 +236,9 @@ async function main() {
|
|
|
234
236
|
case 'task':
|
|
235
237
|
await taskCmd.run(subcommand, restArgs, flags);
|
|
236
238
|
break;
|
|
239
|
+
case 'doctor':
|
|
240
|
+
await doctorCmd.run(subcommand, restArgs, flags);
|
|
241
|
+
break;
|
|
237
242
|
// Convenience aliases
|
|
238
243
|
case 'setup':
|
|
239
244
|
await setupCmd.setup(flags);
|
|
@@ -360,6 +365,7 @@ const HELP_TARGETS = {
|
|
|
360
365
|
git: f => gitCmd.run(undefined, [], f),
|
|
361
366
|
queue: f => queueCmd.run(undefined, [], f),
|
|
362
367
|
task: f => taskCmd.run(undefined, [], f),
|
|
368
|
+
doctor: f => doctorCmd.run(undefined, [], f),
|
|
363
369
|
org: f => orgCmd.run(undefined, [], f),
|
|
364
370
|
billing: f => billingCmd.run(undefined, [], f),
|
|
365
371
|
keys: f => keysCmd.run(undefined, [], f),
|
|
@@ -406,6 +412,7 @@ Commands:
|
|
|
406
412
|
git Hosted git repositories — repos, commits, branches, history
|
|
407
413
|
queue Durable job queue — enqueue work, retried against an HTTP consumer
|
|
408
414
|
task Agent-task queue — file, claim, and resolve units of work
|
|
415
|
+
doctor Org-wide consistency check — funnels, webhooks, domains, containers
|
|
409
416
|
payments Take payments with Stripe Checkout (connect, charge, refund)
|
|
410
417
|
webhook Manage inbound webhook endpoints and inspect deliveries
|
|
411
418
|
email Manage mailboxes, send/read email, templates, and campaigns
|
|
@@ -126,7 +126,7 @@ describe('container.getContainerLogs', () => {
|
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
128
|
describe('container.EXPOSES', () => {
|
|
129
|
-
it('covers the
|
|
129
|
+
it('covers the 8 container endpoints', () => {
|
|
130
130
|
expect(container.EXPOSES).toEqual([
|
|
131
131
|
'POST /container/orgs/{org_id}/containers',
|
|
132
132
|
'GET /container/orgs/{org_id}/containers',
|
|
@@ -134,6 +134,40 @@ describe('container.EXPOSES', () => {
|
|
|
134
134
|
'DELETE /container/orgs/{org_id}/containers/{id}',
|
|
135
135
|
'POST /container/orgs/{org_id}/containers/{id}/deploy',
|
|
136
136
|
'GET /container/orgs/{org_id}/containers/{id}/logs',
|
|
137
|
+
'POST /container/orgs/{org_id}/containers/{id}/domain',
|
|
138
|
+
'DELETE /container/orgs/{org_id}/containers/{id}/domain',
|
|
137
139
|
]);
|
|
138
140
|
});
|
|
139
141
|
});
|
|
142
|
+
describe('container custom domain', () => {
|
|
143
|
+
it('bindDomain POSTs {domain} and returns the binding', async () => {
|
|
144
|
+
fetchMock.mockResolvedValueOnce(ok({
|
|
145
|
+
container_id: C_ID, custom_domain: 'app.synthesisdaily.com',
|
|
146
|
+
origin: 'svc-abc.run.app', status: 'active',
|
|
147
|
+
}));
|
|
148
|
+
const b = await container.bindDomain(API_KEY, ORG_ID, C_ID, 'app.synthesisdaily.com');
|
|
149
|
+
expect(b.status).toBe('active');
|
|
150
|
+
expect(b.origin).toBe('svc-abc.run.app');
|
|
151
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
152
|
+
expect(url).toBe(`https://api.myapihq.com/container/orgs/${ORG_ID}/containers/${C_ID}/domain`);
|
|
153
|
+
expect(init.method).toBe('POST');
|
|
154
|
+
expect(JSON.parse(init.body)).toEqual({ domain: 'app.synthesisdaily.com' });
|
|
155
|
+
});
|
|
156
|
+
it('bindDomain surfaces 422 when the container is not deployed', async () => {
|
|
157
|
+
fetchMock.mockResolvedValueOnce(fail('container_not_deployed', 'container not deployed', 422));
|
|
158
|
+
await expect(container.bindDomain(API_KEY, ORG_ID, C_ID, 'app.x.com'))
|
|
159
|
+
.rejects.toMatchObject({ status: 422 });
|
|
160
|
+
});
|
|
161
|
+
it('bindDomain surfaces 409 when a domain is already bound', async () => {
|
|
162
|
+
fetchMock.mockResolvedValueOnce(fail('domain_conflict', 'domain already bound', 409));
|
|
163
|
+
await expect(container.bindDomain(API_KEY, ORG_ID, C_ID, 'app.x.com'))
|
|
164
|
+
.rejects.toMatchObject({ status: 409 });
|
|
165
|
+
});
|
|
166
|
+
it('unbindDomain DELETEs the domain endpoint', async () => {
|
|
167
|
+
fetchMock.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
168
|
+
await container.unbindDomain(API_KEY, ORG_ID, C_ID);
|
|
169
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
170
|
+
expect(url).toBe(`https://api.myapihq.com/container/orgs/${ORG_ID}/containers/${C_ID}/domain`);
|
|
171
|
+
expect(init.method).toBe('DELETE');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -15,6 +15,8 @@ Handles domain registration, assignment to orgs, and edge (CDN/security) setting
|
|
|
15
15
|
<!-- llm:start -->
|
|
16
16
|
Domains are how you take a funnel from `your-org.makeautonomous.com` to `your-real-brand.com`. The flow is: check availability, register (deducts credits), assign to an org, watch status until DNS propagates. From that point, your org's funnel serves at `https://yourdomain.com`. SSL provisions automatically a few minutes after status flips to `active`.
|
|
17
17
|
|
|
18
|
+
A registered domain isn't only for funnels: a **deployed container** can be served on a custom domain or subdomain too — bind it with `myapi container domain <id> <domain>` (see `my-container-api`). Funnels are static sites; containers are dynamic apps. Either way, the parent domain must be registered here first.
|
|
19
|
+
|
|
18
20
|
You can also import existing domains (without re-registering) and tune CDN/security settings per-domain.
|
|
19
21
|
|
|
20
22
|
Without a domain, funnels still work on the free `*.makeautonomous.com` preview subdomain.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.2",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"lint:changelog": "node ../../scripts/lint-changelog.js"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@myapihq/sdk": "^1.3.
|
|
32
|
+
"@myapihq/sdk": "^1.3.2"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.6.0",
|