fix: drop domain from computed remote name when subdomain present (#22376)
## Context Closes twentyhq/core-team-issues#2619 When adding a remote with `yarn twenty remote:add` and a URL like `https://martin-s-workspace.twenty.com`, the computed remote name ended up as `martin-s-workspace-twenty-com`. The apex domain (`twenty.com`) should be dropped when a subdomain is present, so the name should be `martin-s-workspace`. ## What changed - Extracted `deriveRemoteName` from `remote/index.ts` into its own module `remote/derive-remote-name.ts`. - When the host has a subdomain (more than two labels), only the subdomain labels are used (joined with dashes) — the apex domain is dropped. - Hosts with no subdomain keep the full host (`twenty.com` → `twenty-com`). - Single-label hosts like `localhost` are preserved. - IPv4 addresses are kept intact (`127.0.0.1` → `127-0-0-1`). - Invalid URLs still fall back to `remote`. ## Tests Added `derive-remote-name.spec.ts` covering subdomain, multi-label subdomain, apex-only host, `localhost`, IPv4, and invalid-URL cases. All 6 pass. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22376?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { deriveRemoteName } from '@/cli/commands/remote/derive-remote-name';
|
||||
|
||||
describe('deriveRemoteName', () => {
|
||||
it('should use only the subdomain when the host has one', () => {
|
||||
expect(deriveRemoteName('https://martin-s-workspace.twenty.com')).toBe(
|
||||
'martin-s-workspace',
|
||||
);
|
||||
});
|
||||
|
||||
it('should join multiple subdomain labels with dashes', () => {
|
||||
expect(deriveRemoteName('https://app.staging.twenty.com')).toBe(
|
||||
'app-staging',
|
||||
);
|
||||
});
|
||||
|
||||
it('should drop the tld when there is no subdomain', () => {
|
||||
expect(deriveRemoteName('https://twenty.com')).toBe('twenty');
|
||||
});
|
||||
|
||||
it('should handle single-label hosts like localhost', () => {
|
||||
expect(deriveRemoteName('http://localhost:3000')).toBe('localhost');
|
||||
});
|
||||
|
||||
it('should keep IPv4 addresses intact', () => {
|
||||
expect(deriveRemoteName('http://127.0.0.1:3000')).toBe('127-0-0-1');
|
||||
});
|
||||
|
||||
it('should fall back to "remote" for invalid URLs', () => {
|
||||
expect(deriveRemoteName('not a url')).toBe('remote');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
const IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
|
||||
export const deriveRemoteName = (url: string): string => {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
|
||||
if (IPV4_REGEX.test(hostname)) {
|
||||
return hostname.replace(/\./g, '-');
|
||||
}
|
||||
|
||||
const labels = hostname.split('.');
|
||||
|
||||
if (labels.length > 2) {
|
||||
return labels.slice(0, -2).join('-');
|
||||
}
|
||||
|
||||
if (labels.length === 2) {
|
||||
return labels[0];
|
||||
}
|
||||
|
||||
return labels.join('-');
|
||||
} catch {
|
||||
return 'remote';
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { deriveRemoteName } from '@/cli/commands/remote/derive-remote-name';
|
||||
import { authLogin } from '@/cli/operations/login';
|
||||
import { authLoginOAuth } from '@/cli/operations/login-oauth';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
@@ -9,16 +10,6 @@ import type { Command } from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import { normalizeUrl } from 'twenty-shared/utils';
|
||||
|
||||
const deriveRemoteName = (url: string): string => {
|
||||
try {
|
||||
const hostname = new URL(url).hostname;
|
||||
|
||||
return hostname.replace(/\./g, '-');
|
||||
} catch {
|
||||
return 'remote';
|
||||
}
|
||||
};
|
||||
|
||||
type AuthMethod = 'OAuth' | 'API key';
|
||||
|
||||
const authenticate = async (
|
||||
|
||||
Reference in New Issue
Block a user