@myapihq/cli 1.3.0 → 1.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.
@@ -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
  }
@@ -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'],
@@ -126,7 +126,7 @@ describe('container.getContainerLogs', () => {
126
126
  });
127
127
  });
128
128
  describe('container.EXPOSES', () => {
129
- it('covers the 6 container endpoints', () => {
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.0",
4
+ "version": "1.3.1",
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.0"
32
+ "@myapihq/sdk": "^1.3.1"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^25.6.0",