diff --git a/.storybook/manager.js b/.storybook/manager.js
index 8d8ed89b5..a4d8ffdfe 100644
--- a/.storybook/manager.js
+++ b/.storybook/manager.js
@@ -2,7 +2,7 @@
import { addons } from 'storybook/manager-api';
// eslint-disable-next-line import-x/extensions
import { create } from 'storybook/theming';
-import { generateDocsHref } from '../src/lib/utils.js';
+import { getDocUrl } from '../src/lib/dev-hub-url.js';
import { enhanceStoryName } from '../src/stories/lib/story-names.js';
// We could create an addon to provide a control that would switch between dark / light
@@ -12,7 +12,7 @@ const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const cleverTheme = create({
base: isDarkMode ? 'dark' : 'light',
brandTitle: 'Clever Cloud components',
- brandUrl: generateDocsHref(),
+ brandUrl: getDocUrl(),
brandImage: isDarkMode ? 'imgs/logo-clever-dark.svg' : 'imgs/logo-clever-light.svg',
});
diff --git a/docs/contributing/urls.md b/docs/contributing/urls.md
new file mode 100644
index 000000000..5ab6a3cc7
--- /dev/null
+++ b/docs/contributing/urls.md
@@ -0,0 +1,130 @@
+---
+kind: '👋 Contributing'
+title: 'Documentation and asset URLs'
+---
+
+# Documentation and asset URLs
+
+When working on components, you'll often need to link to documentation, developer hub pages, or static assets. This guide explains how to handle these URLs correctly.
+
+## Documentation URLs
+
+Use `getDocUrl()` to generate URLs for documentation pages on the Clever Cloud developer hub.
+
+### Import the helper
+
+```js
+import { getDocUrl } from '../../lib/dev-hub-url.js';
+```
+
+
+
+### Use the helper
+
+```js
+const url = getDocUrl('/billing/unified-invoices');
+// => 'https://www.clever.cloud/developers/doc/billing/unified-invoices'
+
+// Empty argument defaults to doc root
+const url = getDocUrl();
+// => 'https://www.clever.cloud/developers/doc'
+```
+
+**_🧐 OBSERVATIONS:_**
+
+* Always use a leading slash: `getDocUrl('/path/to/doc')`.
+* You can use the helper directly in templates, as a constant, or in translation files.
+* The helper preserves query parameters and URL fragments.
+
+### Usage in translation files
+
+You can use `getDocUrl()` in translation files to generate documentation links:
+
+```js
+import { getDocUrl } from '../lib/dev-hub-url.js';
+
+export const translations = {
+ 'my-component.doc-link': () => sanitize`
+ Read more in the documentation.
+ `,
+};
+```
+
+## Developer hub URLs
+
+Use `getDevHubUrl()` to generate URLs for general developer hub pages (not under `/doc/`).
+
+### Import the helper
+
+```js
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
+```
+
+### Use the helper
+
+```js
+const url = getDevHubUrl('/api/v4');
+// => 'https://www.clever.cloud/developers/api/v4'
+
+// Supports fragments and query params
+const url = getDevHubUrl('/api/howto/#oauth1');
+// => 'https://www.clever.cloud/developers/api/howto/#oauth1'
+```
+
+## Asset URLs
+
+Use `getAssetUrl()` to generate URLs for static assets that are not part of the components project.
+
+### Import the helper
+
+```js
+import { getAssetUrl } from '../../lib/assets-url.js';
+```
+
+### Use the helper
+
+```js
+const url = getAssetUrl('/logos/java-jar.svg');
+// => 'https://assets.clever-cloud.com/logos/java-jar.svg'
+
+// Preserves query parameters and fragments
+const url = getAssetUrl('/images/logo.png?v=2');
+// => 'https://assets.clever-cloud.com/images/logo.png?v=2'
+```
+
+### Helper functions for common assets
+
+The `remote-assets.js` file provides convenient wrapper functions for frequently used assets:
+
+```js
+import { getFlagUrl, getInfraProviderLogoUrl } from '../../lib/remote-assets.js';
+
+// Country flags
+const flag = getFlagUrl('fr');
+// => 'https://assets.clever-cloud.com/flags/fr.svg'
+
+// Infrastructure provider logos
+const logo = getInfraProviderLogoUrl('ovh');
+// => 'https://assets.clever-cloud.com/infra/ovh.svg'
+```
+
+## Best practices
+
+### Always use the helpers
+
+**DON'T DO THIS:**
+
+```js
+// ❌ Hardcoded URLs
+const url = 'https://www.clever.cloud/developers/doc/addons/postgresql';
+const asset = 'https://assets.clever-cloud.com/logos/java-jar.svg';
+```
+
+**DO THIS:**
+
+```js
+// ✅ Use the helpers
+const url = getDocUrl('/addons/postgresql');
+const asset = getAssetUrl('/logos/java-jar.svg');
+```
+
diff --git a/docs/getting-started/custom-base-urls.md b/docs/getting-started/custom-base-urls.md
new file mode 100644
index 000000000..22b82d027
--- /dev/null
+++ b/docs/getting-started/custom-base-urls.md
@@ -0,0 +1,68 @@
+---
+kind: '🚀 Getting started'
+title: 'Customizing base URLs'
+---
+
+# Customizing base URLs
+
+By default, Clever Cloud components generate URLs that point to:
+* `https://www.clever.cloud/developers` for documentation and developer hub links
+* `https://assets.clever-cloud.com` for static assets
+
+You can override these base URLs.
+
+## Customizing the developer hub base URL
+
+Use `setDevHubBaseUrl()` to change the base URL for documentation and developer hub links.
+
+```js
+import { setDevHubBaseUrl } from '@clevercloud/components/dist/dev-hub-url.js';
+
+// Set a custom base URL
+setDevHubBaseUrl('https://staging.clever-cloud.com/developers');
+
+// Now import components
+import '@clevercloud/components/dist/cc-addon-info.js';
+```
+
+After this configuration, all documentation links generated by components will use your custom base URL:
+
+* `getDocUrl('/addons/postgresql')` → `https://staging.clever-cloud.com/developers/doc/addons/postgresql`
+* `getDevHubUrl('/api/v4')` → `https://staging.clever-cloud.com/developers/api/v4`
+
+## Customizing the assets base URL
+
+Use `setAssetsBaseUrl()` to change the base URL for static assets like logos, flags, and icons.
+
+```js
+import { setAssetsBaseUrl } from '@clevercloud/components/dist/assets-url.js';
+
+// Set a custom assets URL
+setAssetsBaseUrl('https://cdn.example.com');
+
+// Now import components
+import '@clevercloud/components/dist/cc-addon-info.js';
+```
+
+After this configuration, all asset URLs generated by components will use your custom base URL:
+
+* `getAssetUrl('/logos/java-jar.svg')` → `https://cdn.example.com/logos/java-jar.svg`
+* `getFlagUrl('fr')` → `https://cdn.example.com/flags/fr.svg`
+
+## Important: call before importing components
+
+You **must** call `setDevHubBaseUrl()` and `setAssetsBaseUrl()` **before** importing any components. Otherwise, some URLs may still use the default base URLs.
+
+```js
+import { setDevHubBaseUrl } from '@clevercloud/components/dist/dev-hub-url.js';
+import { setAssetsBaseUrl } from '@clevercloud/components/dist/assets-url.js';
+
+// Configure base URLs first
+setDevHubBaseUrl('https://staging.clever-cloud.com/developers');
+setAssetsBaseUrl('https://cdn.example.com');
+
+// Then import components
+import '@clevercloud/components/dist/cc-addon-info.js';
+import '@clevercloud/components/dist/cc-invoice-list.js';
+```
+
diff --git a/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-keycloak.js b/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-keycloak.js
index e40211152..8ed195679 100644
--- a/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-keycloak.js
+++ b/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-keycloak.js
@@ -1,6 +1,6 @@
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { notifyError, notifySuccess } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonCredentialsBetaClient } from './cc-addon-credentials-beta.client.js';
@@ -30,7 +30,7 @@ const LOADING_STATE = {
],
docLink: {
text: i18n('cc-addon-credentials-beta.doc-link.keycloak'),
- href: generateDocsHref('/addons/keycloak/#secured-multi-instances'),
+ href: getDocUrl('/addons/keycloak/#secured-multi-instances'),
},
},
},
diff --git a/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-otoroshi.js b/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-otoroshi.js
index d425ce0e2..7db62ee63 100644
--- a/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-otoroshi.js
+++ b/src/components/cc-addon-credentials-beta/cc-addon-credentials-beta.smart-otoroshi.js
@@ -1,7 +1,7 @@
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { notifyError, notifySuccess } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonCredentialsBetaClient } from './cc-addon-credentials-beta.client.js';
@@ -29,7 +29,7 @@ const LOADING_STATE = {
],
docLink: {
text: i18n('cc-addon-credentials-beta.doc-link.otoroshi-ng'),
- href: generateDocsHref('/addons/otoroshi/#use-otoroshi-in-a-network-group'),
+ href: getDocUrl('/addons/otoroshi/#use-otoroshi-in-a-network-group'),
},
},
api: {
@@ -58,7 +58,7 @@ const LOADING_STATE = {
],
docLink: {
text: i18n('cc-addon-credentials-beta.doc-link.otoroshi-api'),
- href: generateDocsHref('/addons/otoroshi/#manage-otoroshi-from-its-api'),
+ href: getDocUrl('/addons/otoroshi/#manage-otoroshi-from-its-api'),
},
},
},
diff --git a/src/components/cc-addon-header/cc-addon-header.smart-keycloak.js b/src/components/cc-addon-header/cc-addon-header.smart-keycloak.js
index 5842c4352..0689bcba8 100644
--- a/src/components/cc-addon-header/cc-addon-header.smart-keycloak.js
+++ b/src/components/cc-addon-header/cc-addon-header.smart-keycloak.js
@@ -1,13 +1,13 @@
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { notify, notifyError } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonHeaderClient } from './cc-addon-header.client.js';
import './cc-addon-header.js';
-const DOCS_URL = generateDocsHref(`/addons/keycloak`);
+const DOCS_URL = getDocUrl(`/addons/keycloak`);
const PROVIDER_ID = 'keycloak';
/**
diff --git a/src/components/cc-addon-header/cc-addon-header.smart-matomo.js b/src/components/cc-addon-header/cc-addon-header.smart-matomo.js
index ef57a2142..b8b1a9fbc 100644
--- a/src/components/cc-addon-header/cc-addon-header.smart-matomo.js
+++ b/src/components/cc-addon-header/cc-addon-header.smart-matomo.js
@@ -1,13 +1,13 @@
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { notify, notifyError } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonHeaderClient } from './cc-addon-header.client.js';
import './cc-addon-header.js';
-const DOCS_URL = generateDocsHref(`/addons/matomo`);
+const DOCS_URL = getDocUrl(`/addons/matomo`);
const PROVIDER_ID = 'matomo';
/**
diff --git a/src/components/cc-addon-header/cc-addon-header.smart-metabase.js b/src/components/cc-addon-header/cc-addon-header.smart-metabase.js
index b3930c8d3..9cf301f7a 100644
--- a/src/components/cc-addon-header/cc-addon-header.smart-metabase.js
+++ b/src/components/cc-addon-header/cc-addon-header.smart-metabase.js
@@ -1,13 +1,13 @@
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { notify, notifyError } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonHeaderClient } from './cc-addon-header.client.js';
import './cc-addon-header.js';
-const DOCS_URL = generateDocsHref(`/addons/metabase`);
+const DOCS_URL = getDocUrl(`/addons/metabase`);
const PROVIDER_ID = 'metabase';
/**
diff --git a/src/components/cc-addon-header/cc-addon-header.smart-otoroshi.js b/src/components/cc-addon-header/cc-addon-header.smart-otoroshi.js
index 7ce0931f2..30bc78cac 100644
--- a/src/components/cc-addon-header/cc-addon-header.smart-otoroshi.js
+++ b/src/components/cc-addon-header/cc-addon-header.smart-otoroshi.js
@@ -1,13 +1,13 @@
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { notify, notifyError } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonHeaderClient } from './cc-addon-header.client.js';
import './cc-addon-header.js';
-const DOCS_URL = generateDocsHref(`/addons/otoroshi`);
+const DOCS_URL = getDocUrl(`/addons/otoroshi`);
const PROVIDER_ID = 'otoroshi';
/**
diff --git a/src/components/cc-addon-info/cc-addon-info.client.js b/src/components/cc-addon-info/cc-addon-info.client.js
index 67b9596ab..09f7085ef 100644
--- a/src/components/cc-addon-info/cc-addon-info.client.js
+++ b/src/components/cc-addon-info/cc-addon-info.client.js
@@ -4,8 +4,8 @@ import { get as getAddon } from '@clevercloud/client/esm/api/v2/addon.js';
import { getGrafanaOrganisation } from '@clevercloud/client/esm/api/v4/saas.js';
// @ts-expect-error FIXME: remove when clever-client exports types
import { ONE_SECOND } from '@clevercloud/client/esm/with-cache.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { sendToApi } from '../../lib/send-to-api.js';
-import { generateDevHubHref } from '../../lib/utils.js';
/**
* @typedef {import('./cc-addon-info.types.js').AddonInfoStateLoaded} AddonInfoStateLoaded
@@ -178,7 +178,7 @@ export function formatVersionState(operatorVersionInfo) {
installed: operatorVersionInfo.installed,
available: operatorVersionInfo.available.filter((version) => version !== operatorVersionInfo.installed),
latest: operatorVersionInfo.latest,
- changelogLink: `${generateDevHubHref('/changelog')}`,
+ changelogLink: `${getDevHubUrl('/changelog')}`,
};
}
diff --git a/src/components/cc-addon-info/cc-addon-info.smart-keycloak.js b/src/components/cc-addon-info/cc-addon-info.smart-keycloak.js
index 07b63b9e2..0488401a3 100644
--- a/src/components/cc-addon-info/cc-addon-info.smart-keycloak.js
+++ b/src/components/cc-addon-info/cc-addon-info.smart-keycloak.js
@@ -1,7 +1,7 @@
import { getAssetUrl } from '../../lib/assets-url.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { notifyError, notifySuccess } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonInfoClient, formatVersionState } from './cc-addon-info.client.js';
@@ -72,7 +72,7 @@ defineSmartComponent({
updateComponent('state', LOADING_STATE);
updateComponent('docLink', {
text: i18n('cc-addon-info.doc-link.keycloak'),
- href: generateDocsHref('/addons/keycloak'),
+ href: getDocUrl('/addons/keycloak'),
});
api
diff --git a/src/components/cc-addon-info/cc-addon-info.smart-matomo.js b/src/components/cc-addon-info/cc-addon-info.smart-matomo.js
index 47d0395e4..c775d4896 100644
--- a/src/components/cc-addon-info/cc-addon-info.smart-matomo.js
+++ b/src/components/cc-addon-info/cc-addon-info.smart-matomo.js
@@ -1,7 +1,7 @@
import { getAssetUrl } from '../../lib/assets-url.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { sendToApi } from '../../lib/send-to-api.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonInfoClient } from './cc-addon-info.client.js';
@@ -87,7 +87,7 @@ defineSmartComponent({
updateComponent('state', LOADING_STATE);
updateComponent('docLink', {
text: i18n('cc-addon-info.doc-link.matomo'),
- href: generateDocsHref('/addons/matomo'),
+ href: getDocUrl('/addons/matomo'),
});
api
diff --git a/src/components/cc-addon-info/cc-addon-info.smart-metabase.js b/src/components/cc-addon-info/cc-addon-info.smart-metabase.js
index bcae440f8..796bcd210 100644
--- a/src/components/cc-addon-info/cc-addon-info.smart-metabase.js
+++ b/src/components/cc-addon-info/cc-addon-info.smart-metabase.js
@@ -1,7 +1,7 @@
import { getAssetUrl } from '../../lib/assets-url.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { notifyError, notifySuccess } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonInfoClient, formatVersionState } from './cc-addon-info.client.js';
@@ -82,7 +82,7 @@ defineSmartComponent({
updateComponent('state', LOADING_STATE);
updateComponent('docLink', {
text: i18n('cc-addon-info.doc-link.metabase'),
- href: generateDocsHref('/addons/metabase'),
+ href: getDocUrl('/addons/metabase'),
});
api
diff --git a/src/components/cc-addon-info/cc-addon-info.smart-otoroshi.js b/src/components/cc-addon-info/cc-addon-info.smart-otoroshi.js
index 14cbc97c8..0c6ee4d7c 100644
--- a/src/components/cc-addon-info/cc-addon-info.smart-otoroshi.js
+++ b/src/components/cc-addon-info/cc-addon-info.smart-otoroshi.js
@@ -1,7 +1,7 @@
import { getAssetUrl } from '../../lib/assets-url.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { notifyError, notifySuccess } from '../../lib/notifications.js';
import { defineSmartComponent } from '../../lib/smart/define-smart-component.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-smart-container/cc-smart-container.js';
import { CcAddonInfoClient, formatVersionState } from './cc-addon-info.client.js';
@@ -76,7 +76,7 @@ defineSmartComponent({
updateComponent('state', LOADING_STATE);
updateComponent('docLink', {
text: i18n('cc-addon-info.doc-link.otoroshi'),
- href: generateDocsHref('/addons/otoroshi'),
+ href: getDocUrl('/addons/otoroshi'),
});
api
diff --git a/src/components/cc-addon-info/cc-addon-info.stories.js b/src/components/cc-addon-info/cc-addon-info.stories.js
index bd6bc95c1..7ba384ebc 100644
--- a/src/components/cc-addon-info/cc-addon-info.stories.js
+++ b/src/components/cc-addon-info/cc-addon-info.stories.js
@@ -1,4 +1,4 @@
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import {
azimuttInfo,
configInfo,
@@ -222,7 +222,7 @@ export const materia = makeStory(conf, {
...materiaInfo,
},
innerHTML: `
-
Materia KV uses our next generation of serverless distributed database, synchronously-replicated. Free of charge during testing phases, it will be available on a pay-as-you-go flexible pricing. Develop with ease, it's compatible with third-party protocols, such as Redis: learn more.
+ Materia KV uses our next generation of serverless distributed database, synchronously-replicated. Free of charge during testing phases, it will be available on a pay-as-you-go flexible pricing. Develop with ease, it's compatible with third-party protocols, such as Redis: learn more.
`,
},
{
@@ -232,7 +232,7 @@ export const materia = makeStory(conf, {
...materiaInfo,
},
innerHTML: `
- Materia KV uses our next generation of serverless distributed database, synchronously-replicated. Free of charge during testing phases, it will be available on a pay-as-you-go flexible pricing. Develop with ease, it's compatible with third-party protocols, such as Redis: learn more.
+ Materia KV uses our next generation of serverless distributed database, synchronously-replicated. Free of charge during testing phases, it will be available on a pay-as-you-go flexible pricing. Develop with ease, it's compatible with third-party protocols, such as Redis: learn more.
`,
},
],
diff --git a/src/components/cc-domain-management/cc-domain-management.js b/src/components/cc-domain-management/cc-domain-management.js
index 15c2e858e..bb6347b69 100644
--- a/src/components/cc-domain-management/cc-domain-management.js
+++ b/src/components/cc-domain-management/cc-domain-management.js
@@ -11,6 +11,7 @@ import {
iconRemixFlaskLine as iconTest,
} from '../../assets/cc-remix.icons.js';
import { LostFocusController } from '../../controllers/lost-focus-controller.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import {
DomainParseError,
getDomainUrl,
@@ -21,7 +22,6 @@ import {
sortDomains,
} from '../../lib/domain.js';
import { focusBySelector } from '../../lib/focus-helper.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { accessibilityStyles } from '../../styles/accessibility.js';
import { cliCommandsStyles } from '../../styles/cli-commands.js';
import { i18n } from '../../translations/translation.js';
@@ -37,11 +37,11 @@ import '../cc-loader/cc-loader.js';
import '../cc-notice/cc-notice.js';
import { CcDomainAddEvent, CcDomainDeleteEvent, CcDomainMarkAsPrimaryEvent } from './cc-domain-management.events.js';
-const DOMAIN_NAMES_DOCUMENTATION = generateDocsHref(
+const DOMAIN_NAMES_DOCUMENTATION = getDocUrl(
'/administrate/domain-names/#using-a-cleverappsio-free-domain-with-built-in-ssl',
);
-const TLS_CERTIFICATES_DOCUMENTATION = generateDocsHref('/administrate/ssl/');
-const DNS_DOCUMENTATION = generateDocsHref('/administrate/domain-names/');
+const TLS_CERTIFICATES_DOCUMENTATION = getDocUrl('/administrate/ssl');
+const DNS_DOCUMENTATION = getDocUrl('/administrate/domain-names');
/**
* @typedef {import('./cc-domain-management.types.js').DomainManagementDnsInfoState} DomainManagementDnsInfoState
diff --git a/src/components/cc-elasticsearch-info/cc-elasticsearch-info.js b/src/components/cc-elasticsearch-info/cc-elasticsearch-info.js
index 30c42c2e1..e63dfed3c 100644
--- a/src/components/cc-elasticsearch-info/cc-elasticsearch-info.js
+++ b/src/components/cc-elasticsearch-info/cc-elasticsearch-info.js
@@ -2,7 +2,7 @@ import { css, html, LitElement } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { getAssetUrl } from '../../lib/assets-url.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { skeletonStyles } from '../../styles/skeleton.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block/cc-block.js';
@@ -13,7 +13,7 @@ import '../cc-notice/cc-notice.js';
const ELASTICSEARCH_LOGO_URL = getAssetUrl('/logos/elastic.svg');
const KIBANA_LOGO_URL = getAssetUrl('/logos/elasticsearch-kibana.svg');
const APM_LOGO_URL = getAssetUrl('/logos/elasticsearch-apm.svg');
-const ELASTICSEARCH_DOCUMENTATION = generateDocsHref('/addons/elastic/');
+const ELASTICSEARCH_DOCUMENTATION = getDocUrl('/addons/elastic');
const linksSortOrder = ['elasticsearch', 'kibana', 'apm'];
/**
diff --git a/src/components/cc-env-var-form/cc-env-var-form.js b/src/components/cc-env-var-form/cc-env-var-form.js
index 8da70d807..418c09788 100644
--- a/src/components/cc-env-var-form/cc-env-var-form.js
+++ b/src/components/cc-env-var-form/cc-env-var-form.js
@@ -1,6 +1,6 @@
import { css, html, LitElement } from 'lit';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { cliCommandsStyles } from '../../styles/cli-commands.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-details/cc-block-details.js';
@@ -17,7 +17,7 @@ import '../cc-notice/cc-notice.js';
import '../cc-toggle/cc-toggle.js';
import { CcEnvVarFormSubmitEvent } from './cc-env-var-form.events.js';
-const ENV_VAR_DOCUMENTATION = generateDocsHref('/reference/reference-environment-variables/');
+const ENV_VAR_DOCUMENTATION = getDocUrl('/reference/reference-environment-variables');
/**
* @typedef {import('./cc-env-var-form.types.js').EnvVarFormContextType} EnvVarFormContextType
diff --git a/src/components/cc-grafana-info/cc-grafana-info.js b/src/components/cc-grafana-info/cc-grafana-info.js
index 242f77ef7..81b28b299 100644
--- a/src/components/cc-grafana-info/cc-grafana-info.js
+++ b/src/components/cc-grafana-info/cc-grafana-info.js
@@ -1,7 +1,7 @@
import { css, html, LitElement } from 'lit';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { getAssetUrl } from '../../lib/assets-url.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-section/cc-block-section.js';
import '../cc-block/cc-block.js';
@@ -16,7 +16,7 @@ const GRAFANA_LOGO_URL = getAssetUrl('/logos/grafana.svg');
const GRAFANA_HOME_SCREEN = getAssetUrl('/grafana/screens/home.png');
const GRAFANA_RUNTIME_SCREEN = getAssetUrl('/grafana/screens/runtime.png');
const GRAFANA_ADDON_SCREEN = getAssetUrl('/grafana/screens/addon.png');
-const GRAFANA_DOCUMENTATION = generateDocsHref('/metrics');
+const GRAFANA_DOCUMENTATION = getDocUrl('/metrics');
/**
* @typedef {import('./cc-grafana-info.types.js').GrafanaInfoState} GrafanaInfoState
diff --git a/src/components/cc-heptapod-info/cc-heptapod-info.js b/src/components/cc-heptapod-info/cc-heptapod-info.js
index 0986cf428..102bff51f 100644
--- a/src/components/cc-heptapod-info/cc-heptapod-info.js
+++ b/src/components/cc-heptapod-info/cc-heptapod-info.js
@@ -2,7 +2,7 @@ import { css, html, LitElement } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { getAssetUrl } from '../../lib/assets-url.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { skeletonStyles } from '../../styles/skeleton.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block/cc-block.js';
@@ -17,7 +17,7 @@ const SKELETON_STATISTICS = {
price: 17.5,
};
-const HEPTAPOD_DOCUMENTATION = generateDocsHref('/addons/heptapod/');
+const HEPTAPOD_DOCUMENTATION = getDocUrl('/addons/heptapod');
const HEPTAPOD_LOGO_URL = getAssetUrl('/logos/heptapod.svg');
/**
diff --git a/src/components/cc-invoice-list/cc-invoice-list.js b/src/components/cc-invoice-list/cc-invoice-list.js
index bc8b00c73..48875af9b 100644
--- a/src/components/cc-invoice-list/cc-invoice-list.js
+++ b/src/components/cc-invoice-list/cc-invoice-list.js
@@ -1,7 +1,8 @@
import { css, html, LitElement } from 'lit';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { ResizeController } from '../../controllers/resize-controller.js';
-import { generateDocsHref, sortBy, unique } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
+import { sortBy, unique } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-section/cc-block-section.js';
import '../cc-block/cc-block.js';
@@ -13,7 +14,7 @@ import '../cc-select/cc-select.js';
import '../cc-toggle/cc-toggle.js';
const BREAKPOINTS = [520];
-const INVOICE_DOCUMENTATION = generateDocsHref('/billing/unified-invoices/');
+const INVOICE_DOCUMENTATION = getDocUrl('/billing/unified-invoices');
/**
* @param {string} dateString
diff --git a/src/components/cc-jenkins-info/cc-jenkins-info.js b/src/components/cc-jenkins-info/cc-jenkins-info.js
index 449c8bdf1..4415e0414 100644
--- a/src/components/cc-jenkins-info/cc-jenkins-info.js
+++ b/src/components/cc-jenkins-info/cc-jenkins-info.js
@@ -1,7 +1,7 @@
import { css, html, LitElement } from 'lit';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { getAssetUrl } from '../../lib/assets-url.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { skeletonStyles } from '../../styles/skeleton.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-section/cc-block-section.js';
@@ -12,7 +12,7 @@ import '../cc-link/cc-link.js';
import '../cc-notice/cc-notice.js';
const JENKINS_LOGO_URL = getAssetUrl('/logos/jenkins.svg');
-const JENKINS_DOCUMENTATION = generateDocsHref('/deploy/addon/jenkins/');
+const JENKINS_DOCUMENTATION = getDocUrl('/deploy/addon/jenkins');
/**
* @typedef {import('./cc-jenkins-info.types.js').JenkinsInfoState} JenkinsInfoState
diff --git a/src/components/cc-matomo-info/cc-matomo-info.js b/src/components/cc-matomo-info/cc-matomo-info.js
index d08766263..77a555fdc 100644
--- a/src/components/cc-matomo-info/cc-matomo-info.js
+++ b/src/components/cc-matomo-info/cc-matomo-info.js
@@ -1,7 +1,7 @@
import { css, html, LitElement } from 'lit';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { getAssetUrl } from '../../lib/assets-url.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { skeletonStyles } from '../../styles/skeleton.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-section/cc-block-section.js';
@@ -18,7 +18,7 @@ const MATOMO_LOGO_URL = getAssetUrl('/logos/matomo.svg');
const PHP_LOGO_URL = getAssetUrl('/logos/php.svg');
const MYSQL_LOGO_URL = getAssetUrl('/logos/mysql.svg');
const REDIS_LOGO_URL = getAssetUrl('/logos/redis.svg');
-const MATOMO_DOCUMENTATION = generateDocsHref('/deploy/addon/matomo/');
+const MATOMO_DOCUMENTATION = getDocUrl('/deploy/addon/matomo');
/**
* @typedef {import('./cc-matomo-info.types.js').MatomoInfoState} MatomoInfoState
diff --git a/src/components/cc-oauth-consumer-form/cc-oauth-consumer-form.js b/src/components/cc-oauth-consumer-form/cc-oauth-consumer-form.js
index e123b639c..1e726ef26 100644
--- a/src/components/cc-oauth-consumer-form/cc-oauth-consumer-form.js
+++ b/src/components/cc-oauth-consumer-form/cc-oauth-consumer-form.js
@@ -2,11 +2,11 @@ import { css, html, LitElement } from 'lit';
import { createRef, ref } from 'lit/directives/ref.js';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { ResizeController } from '../../controllers/resize-controller.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { formSubmit } from '../../lib/form/form-submit-directive.js';
import { getFormDataMap } from '../../lib/form/form-utils.js';
import { Validation } from '../../lib/form/validation.js';
-import { generateDevHubHref } from '../../lib/utils.js';
import { accessibilityStyles } from '../../styles/accessibility.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-section/cc-block-section.js';
@@ -22,7 +22,7 @@ import {
const BREAKPOINTS = [450, 550, 750];
-const OAUTH_CONSUMER_DOCUMENTATION = generateDevHubHref('/api/howto/#oauth1');
+const OAUTH_CONSUMER_DOCUMENTATION = getDevHubUrl('/api/howto/#oauth1');
const URL_VALIDATOR = {
/**
diff --git a/src/components/cc-orga-member-list/cc-orga-member-list.js b/src/components/cc-orga-member-list/cc-orga-member-list.js
index e47e8693a..711c4e566 100644
--- a/src/components/cc-orga-member-list/cc-orga-member-list.js
+++ b/src/components/cc-orga-member-list/cc-orga-member-list.js
@@ -4,9 +4,9 @@ import { createRef, ref } from 'lit/directives/ref.js';
import { repeat } from 'lit/directives/repeat.js';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
import { LostFocusController } from '../../controllers/lost-focus-controller.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { formSubmit } from '../../lib/form/form-submit-directive.js';
import { Validation } from '../../lib/form/validation.js';
-import { generateDocsHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-badge/cc-badge.js';
import '../cc-block-section/cc-block-section.js';
@@ -21,7 +21,7 @@ import { CcOrgaMemberCard } from '../cc-orga-member-card/cc-orga-member-card.js'
import '../cc-select/cc-select.js';
import { CcOrgaMemberInviteEvent } from './cc-orga-member-list.events.js';
-const ORGA_MEMBER_DOCUMENTATION = generateDocsHref('/account/administrate-organization/');
+const ORGA_MEMBER_DOCUMENTATION = getDocUrl('/account/administrate-organization');
/**
* @typedef {import('./cc-orga-member-list.types.js').OrgaMemberListState} OrgaMemberListState
diff --git a/src/components/cc-ssh-key-list/cc-ssh-key-list.js b/src/components/cc-ssh-key-list/cc-ssh-key-list.js
index 3c8388def..577334c5b 100644
--- a/src/components/cc-ssh-key-list/cc-ssh-key-list.js
+++ b/src/components/cc-ssh-key-list/cc-ssh-key-list.js
@@ -9,11 +9,12 @@ import {
iconRemixKey_2Fill as iconKey,
} from '../../assets/cc-remix.icons.js';
import { LostFocusController } from '../../controllers/lost-focus-controller.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { fakeString } from '../../lib/fake-strings.js';
import { focusBySelector } from '../../lib/focus-helper.js';
import { formSubmit } from '../../lib/form/form-submit-directive.js';
import { Validation } from '../../lib/form/validation.js';
-import { generateDocsHref, sortBy } from '../../lib/utils.js';
+import { sortBy } from '../../lib/utils.js';
import { skeletonStyles } from '../../styles/skeleton.js';
import { i18n } from '../../translations/translation.js';
import '../cc-badge/cc-badge.js';
@@ -27,7 +28,7 @@ import '../cc-link/cc-link.js';
import '../cc-notice/cc-notice.js';
import { CcSshKeyCreateEvent, CcSshKeyDeleteEvent, CcSshKeyImportEvent } from './cc-ssh-key-list.events.js';
-const SSH_KEY_DOCUMENTATION = generateDocsHref('/account/ssh-keys-management/');
+const SSH_KEY_DOCUMENTATION = getDocUrl('/account/ssh-keys-management');
/**
* @type {SshKeyState[]}
diff --git a/src/components/cc-tcp-redirection-form/cc-tcp-redirection-form.js b/src/components/cc-tcp-redirection-form/cc-tcp-redirection-form.js
index 5fb7cb194..517a983ed 100644
--- a/src/components/cc-tcp-redirection-form/cc-tcp-redirection-form.js
+++ b/src/components/cc-tcp-redirection-form/cc-tcp-redirection-form.js
@@ -1,6 +1,6 @@
import { css, html, LitElement } from 'lit';
import { iconRemixInformationFill as iconInfo } from '../../assets/cc-remix.icons.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { cliCommandsStyles } from '../../styles/cli-commands.js';
import { i18n } from '../../translations/translation.js';
import '../cc-badge/cc-badge.js';
@@ -11,7 +11,7 @@ import '../cc-link/cc-link.js';
import '../cc-notice/cc-notice.js';
import '../cc-tcp-redirection/cc-tcp-redirection.js';
-const TCP_REDIRECTION_DOCUMENTATION = generateDocsHref('/administrate/tcp-redirections/');
+const TCP_REDIRECTION_DOCUMENTATION = getDocUrl('/administrate/tcp-redirections');
/** @type {TcpRedirectionStateLoading[]} */
const SKELETON_REDIRECTIONS = [{ type: 'loading' }, { type: 'loading' }];
diff --git a/src/components/cc-token-api-creation-form/cc-token-api-creation-form.js b/src/components/cc-token-api-creation-form/cc-token-api-creation-form.js
index 6d618a786..66839b6d6 100644
--- a/src/components/cc-token-api-creation-form/cc-token-api-creation-form.js
+++ b/src/components/cc-token-api-creation-form/cc-token-api-creation-form.js
@@ -10,9 +10,9 @@ import {
} from '../../assets/cc-remix.icons.js';
import { DateFormatter } from '../../lib/date/date-formatter.js';
import { getRelativeDateFromNow, shiftDateField } from '../../lib/date/date-utils.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { FormErrorFocusController } from '../../lib/form/form-error-focus-controller.js';
import { formSubmit } from '../../lib/form/form-submit-directive.js';
-import { generateDevHubHref } from '../../lib/utils.js';
import { cliCommandsStyles } from '../../styles/cli-commands.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-details/cc-block-details.js';
@@ -377,7 +377,7 @@ export class CcTokenApiCreationForm extends LitElement {
${i18n('cc-block-details.cli.text')}
-
+
${i18n('cc-token-api-creation-form.link.doc')}
diff --git a/src/components/cc-token-api-list/cc-token-api-list.js b/src/components/cc-token-api-list/cc-token-api-list.js
index f16211834..d1ecafc81 100644
--- a/src/components/cc-token-api-list/cc-token-api-list.js
+++ b/src/components/cc-token-api-list/cc-token-api-list.js
@@ -12,8 +12,9 @@ import {
} from '../../assets/cc-remix.icons.js';
import { LostFocusController } from '../../controllers/lost-focus-controller.js';
import { ResizeController } from '../../controllers/resize-controller.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { isExpirationClose } from '../../lib/tokens.js';
-import { generateDevHubHref, isStringEmpty } from '../../lib/utils.js';
+import { isStringEmpty } from '../../lib/utils.js';
import { cliCommandsStyles } from '../../styles/cli-commands.js';
import { i18n } from '../../translations/translation.js';
import '../cc-badge/cc-badge.js';
@@ -186,7 +187,7 @@ export class CcTokenApiList extends LitElement {
${i18n('cc-block-details.cli.text')}
-
+
${i18n('cc-token-api-list.link.doc')}
diff --git a/src/components/cc-token-api-list/cc-token-api-list.stories.js b/src/components/cc-token-api-list/cc-token-api-list.stories.js
index 158b5cd51..f297746a8 100644
--- a/src/components/cc-token-api-list/cc-token-api-list.stories.js
+++ b/src/components/cc-token-api-list/cc-token-api-list.stories.js
@@ -1,5 +1,5 @@
import { shiftDateField } from '../../lib/date/date-utils.js';
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { makeStory, storyWait } from '../../stories/lib/make-story.js';
import './cc-token-api-list.js';
@@ -20,7 +20,7 @@ const conf = {
const now = new Date();
-const apiTokenUpdateHref = generateDocsHref(
+const apiTokenUpdateHref = getDocUrl(
'/clever-components/?path=/story/🛠-profile-cc-token-api-update-form--default-story',
);
diff --git a/src/components/cc-token-api-update-form/cc-token-api-update-form.js b/src/components/cc-token-api-update-form/cc-token-api-update-form.js
index 24896b30c..4a302ada0 100644
--- a/src/components/cc-token-api-update-form/cc-token-api-update-form.js
+++ b/src/components/cc-token-api-update-form/cc-token-api-update-form.js
@@ -3,8 +3,8 @@ import {
iconRemixArrowLeftLine as iconGoBack,
iconRemixInformationFill as iconInfo,
} from '../../assets/cc-remix.icons.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { formSubmit } from '../../lib/form/form-submit-directive.js';
-import { generateDevHubHref } from '../../lib/utils.js';
import { cliCommandsStyles } from '../../styles/cli-commands.js';
import { i18n } from '../../translations/translation.js';
import '../cc-block-details/cc-block-details.js';
@@ -79,7 +79,7 @@ export class CcTokenApiUpdateForm extends LitElement {
${i18n('cc-block-details.cli.text')}
-
+
${i18n('cc-token-api-update-form.link.doc')}
diff --git a/src/components/cc-token-api-update-form/cc-token-api-update-form.stories.js b/src/components/cc-token-api-update-form/cc-token-api-update-form.stories.js
index e7cb1d8f5..b70c3011a 100644
--- a/src/components/cc-token-api-update-form/cc-token-api-update-form.stories.js
+++ b/src/components/cc-token-api-update-form/cc-token-api-update-form.stories.js
@@ -1,8 +1,8 @@
-import { generateDocsHref } from '../../lib/utils.js';
+import { getDocUrl } from '../../lib/dev-hub-url.js';
import { makeStory, storyWait } from '../../stories/lib/make-story.js';
import './cc-token-api-update-form.js';
-const CC_TOKEN_API_LIST_STORY_HREF = generateDocsHref(
+const CC_TOKEN_API_LIST_STORY_HREF = getDocUrl(
'/clever-components/?path=/story/🛠-profile-cc-token-api-list--default-story',
);
diff --git a/src/components/cc-token-oauth-list/cc-token-oauth-list.js b/src/components/cc-token-oauth-list/cc-token-oauth-list.js
index 0a2608894..ecd082c00 100644
--- a/src/components/cc-token-oauth-list/cc-token-oauth-list.js
+++ b/src/components/cc-token-oauth-list/cc-token-oauth-list.js
@@ -10,8 +10,8 @@ import {
} from '../../assets/cc-remix.icons.js';
import { LostFocusController } from '../../controllers/lost-focus-controller.js';
import { ResizeController } from '../../controllers/resize-controller.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { isExpirationClose } from '../../lib/tokens.js';
-import { generateDevHubHref } from '../../lib/utils.js';
import { i18n } from '../../translations/translation.js';
import '../cc-badge/cc-badge.js';
import '../cc-block/cc-block.js';
@@ -127,7 +127,7 @@ export class CcTokenOauthList extends LitElement {
-
+
${i18n('cc-token-oauth-list.link.doc')}
diff --git a/src/components/cc-visual-tests-report/cc-visual-tests-report.js b/src/components/cc-visual-tests-report/cc-visual-tests-report.js
index 0fb0e9b48..b8d5abbe3 100644
--- a/src/components/cc-visual-tests-report/cc-visual-tests-report.js
+++ b/src/components/cc-visual-tests-report/cc-visual-tests-report.js
@@ -8,7 +8,7 @@ import {
iconRemixFlowChart as iconWorkflow,
} from '../../assets/cc-remix.icons.js';
import { DateFormatter } from '../../lib/date/date-formatter.js';
-import { generateDevHubHref } from '../../lib/utils.js';
+import { getDevHubUrl } from '../../lib/dev-hub-url.js';
import { accessibilityStyles } from '../../styles/accessibility.js';
import '../cc-block-section/cc-block-section.js';
import '../cc-block/cc-block.js';
@@ -123,7 +123,7 @@ export class CcVisualTestsReport extends LitElement {
} a
* @param {Record} b
@@ -256,39 +247,6 @@ export function sleep(delay) {
});
}
-/**
- * Rely on this helper for every reference to the Clever Cloud docs website
- *
- * @param {string} [path]
- * @returns {string} href
- */
-export function generateDocsHref(path = '') {
- /**
- * Ensure "/doc" is appended after the base URL, then append the provided path
- * If you change the base URL here, you should probably also change it in places where it's still hard coded:
- * - README.md,
- * - CONTRIBUTING.md,
- * - sandbox/index.html,
- * - test/utils.test.js.
- */
- const CC_DOCS_BASE_URL = CC_DEV_HUB_BASE_URL.replace(/\/$/, '') + '/doc/';
- // if a '/' is present at the beginning, the path is considered absolute and it is appended right after the origin
- // we want the path to always be appended after the existing path of the base URL so we remove the first '/' so that it's considered relative
- return new URL(path.replace(/^\//, ''), CC_DOCS_BASE_URL).href;
-}
-
-/**
- * Rely on this helper for every reference to the Clever Cloud developer hub website
- *
- * @param {string} [path]
- * @returns {string} href
- */
-export function generateDevHubHref(path = '') {
- // if a '/' is present at the beginning, the path is considered absolute and it is appended right after the origin
- // we want the path to always be appended after the existing path of the base URL so we remove the first '/' so that it's considered relative
- return new URL(path.replace(/^\//, ''), CC_DEV_HUB_BASE_URL).href;
-}
-
/**
* Checks if a given DOM element is currently visible within a given container's bounds.
*
diff --git a/src/stories/fixtures/addon-info.js b/src/stories/fixtures/addon-info.js
index ae473bbdd..5763a532e 100644
--- a/src/stories/fixtures/addon-info.js
+++ b/src/stories/fixtures/addon-info.js
@@ -1,5 +1,5 @@
import { getAssetUrl } from '../../lib/assets-url.js';
-import { generateDevHubHref, generateDocsHref } from '../../lib/utils.js';
+import { getDevHubUrl, getDocUrl } from '../../lib/dev-hub-url.js';
/**
* @typedef {import('../../components/cc-addon-info/cc-addon-info.types.js').AddonInfoStateBaseProperties} AddonInfoStateBaseProperties
@@ -35,7 +35,7 @@ export const matomoInfo = {
link: 'https://example.com/addon',
},
],
- docUrlLink: generateDocsHref('addons/matomo'),
+ docUrlLink: getDocUrl('addons/matomo'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -62,7 +62,7 @@ export const metabaseInfo = {
link: 'https://example.com/addon',
},
],
- docUrlLink: generateDocsHref('addons/metabase'),
+ docUrlLink: getDocUrl('addons/metabase'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -94,7 +94,7 @@ export const keycloakInfo = {
link: 'https://example.com/addon',
},
],
- docUrlLink: generateDocsHref('addons/keycloak'),
+ docUrlLink: getDocUrl('addons/keycloak'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -121,13 +121,13 @@ export const otoroshiInfo = {
link: 'https://example.com/addon',
},
],
- docUrlLink: generateDocsHref('addons/otoroshi'),
+ docUrlLink: getDocUrl('addons/otoroshi'),
}
/** @type {AddonInfoStateBaseProperties} */
export const materiaInfo = {
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/materia-kv'),
+ docUrlLink: getDocUrl('addons/materia-kv'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -161,7 +161,7 @@ export const jenkinsInfo = {
},
],
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/jenkins'),
+ docUrlLink: getDocUrl('addons/jenkins'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -211,7 +211,7 @@ export const elasticInfo = {
link: 'https://example.com/addon',
},
],
- docUrlLink: generateDocsHref('addons/elastic'),
+ docUrlLink: getDocUrl('addons/elastic'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -222,20 +222,20 @@ export const pulsarInfo = {
latest: '4.0.6',
},
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/pulsar'),
+ docUrlLink: getDocUrl('addons/pulsar'),
}
/** @type {AddonInfoStateBaseProperties} */
export const configInfo = {
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/config-provider'),
+ docUrlLink: getDocUrl('addons/config-provider'),
}
/** @type {AddonInfoStateBaseProperties} */
export const mailpaceInfo = {
plan: 'XS',
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/mailpace'),
+ docUrlLink: getDocUrl('addons/mailpace'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -269,7 +269,7 @@ export const mysqlInfo = {
},
],
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/mysql'),
+ docUrlLink: getDocUrl('addons/mysql'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -309,7 +309,7 @@ export const postgresqlInfo = {
],
creationDate: '2025-06-15T10:30:00Z',
role: 'Primary',
- docUrlLink: generateDocsHref('addons/postgresql'),
+ docUrlLink: getDocUrl('addons/postgresql'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -348,7 +348,7 @@ export const redisInfo = {
},
],
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/redis'),
+ docUrlLink: getDocUrl('addons/redis'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -382,7 +382,7 @@ export const mongodbInfo = {
},
],
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDocsHref('addons/mongodb'),
+ docUrlLink: getDocUrl('addons/mongodb'),
}
/** @type {AddonInfoStateBaseProperties} */
@@ -416,5 +416,5 @@ export const kubernetesInfo = {
latest: '1.33',
},
creationDate: '2025-06-15T10:30:00Z',
- docUrlLink: generateDevHubHref('guides/kubernetes-operator/'),
+ docUrlLink: getDevHubUrl('guides/kubernetes-operator'),
}
diff --git a/src/translations/translations.en.js b/src/translations/translations.en.js
index c85f88235..950f41d16 100644
--- a/src/translations/translations.en.js
+++ b/src/translations/translations.en.js
@@ -1,3 +1,4 @@
+import { getDevHubUrl, getDocUrl } from '../lib/dev-hub-url.js';
import {
prepareFormatDate,
prepareFormatDateOnly,
@@ -15,7 +16,6 @@ import {
} from '../lib/i18n/i18n-number.js';
import { sanitize } from '../lib/i18n/i18n-sanitize.js';
import { preparePlural } from '../lib/i18n/i18n-string.js';
-import { generateDevHubHref, generateDocsHref } from '../lib/utils.js';
/**
* @typedef {import('../components/common.types.js').Flavor} Flavor
@@ -66,7 +66,7 @@ function formatFlavor(flavor) {
}
const getCliInstructions = () =>
- sanitize`To install Clever Tools CLI, follow the instructions from the documentation.`;
+ sanitize`To install Clever Tools CLI, follow the instructions from the documentation.`;
export const translations = {
//#region cc-addon-admin
@@ -253,7 +253,7 @@ export const translations = {
//#endregion
//#region cc-addon-encryption-at-rest-option
'cc-addon-encryption-at-rest-option.description': () =>
- sanitize`Encryption at rest encrypts the entire data disk of your add-on. It prevents reading the stored data in case of a physical access to the hard drive. More information in our documentation.`,
+ sanitize`Encryption at rest encrypts the entire data disk of your add-on. It prevents reading the stored data in case of a physical access to the hard drive. More information in our documentation.`,
'cc-addon-encryption-at-rest-option.title': `Encryption at rest`,
//#endregion
//#region cc-addon-features
@@ -435,12 +435,12 @@ export const translations = {
//#endregion
//#region cc-domain-management
'cc-domain-management.certif.automated': () =>
- sanitize`Whether you use cleverapps.io or your own domain names with applications hosted by Clever Cloud, a Let's Encrypt certificate is automatically issued and renewed for HTTPS/TLS access. No action is required from you, this is all automated. For specific cases, refer to Installing TLS Certificates.`,
+ sanitize`Whether you use cleverapps.io or your own domain names with applications hosted by Clever Cloud, a Let's Encrypt certificate is automatically issued and renewed for HTTPS/TLS access. No action is required from you, this is all automated. For specific cases, refer to Installing TLS Certificates.`,
'cc-domain-management.certif.custom': () =>
sanitize`You can provide your own certificate by using the Clever Cloud Certificate Manager.`,
'cc-domain-management.certif.heading': `Secure your application`,
'cc-domain-management.dns.a.desc': () =>
- sanitize`If you choose to use A records, for instance with a root domain (APEX), you'll need to update them yourself. Follow our changelog or check our v4 API documentation for this.
`,
+ sanitize`If you choose to use A records, for instance with a root domain (APEX), you'll need to update them yourself. Follow our changelog or check our v4 API documentation for this.
`,
'cc-domain-management.dns.a.heading': `A records`,
'cc-domain-management.dns.a.label': `A Record values`,
'cc-domain-management.dns.cli.content.diag-conf-command': `Diagnose the current configuration:`,
@@ -455,7 +455,7 @@ export const translations = {
'cc-domain-management.dns.documentation.text': `DNS records - Documentation`,
'cc-domain-management.dns.heading': `Configure your DNS`,
'cc-domain-management.dns.info.desc': () =>
- sanitize`If you are using a dedicated load balancer, refer to its configuration or contact support. Our team can also help you to order such a service. For a domain with no subdomains (APEX) or a subdomain with its own DNS zone, refer to our DNS & Domains documentation.`,
+ sanitize`If you are using a dedicated load balancer, refer to its configuration or contact support. Our team can also help you to order such a service. For a domain with no subdomains (APEX) or a subdomain with its own DNS zone, refer to our DNS & Domains documentation.`,
'cc-domain-management.dns.info.heading': `Dedicated load balancers & specific cases`,
'cc-domain-management.dns.loading-error': `Something went wrong while loading DNS information`,
'cc-domain-management.form.domain.error.contains-path': /** @param {{path: string}} _ */ ({ path }) =>
@@ -468,7 +468,7 @@ export const translations = {
sanitize`For instance: example.com, *.example.com or example.cleverapps.io`,
'cc-domain-management.form.domain.label': `Domain name`,
'cc-domain-management.form.info.cleverapps': () =>
- sanitize`By default, an application is automatically associated to app_id.cleverapps.io as primary domain. You can remove it or change the subdomain freely, but xxx.cleverapps.io should only be used for testing purposes (see our documentation).`,
+ sanitize`By default, an application is automatically associated to app_id.cleverapps.io as primary domain. You can remove it or change the subdomain freely, but xxx.cleverapps.io should only be used for testing purposes (see our documentation).`,
'cc-domain-management.form.info.docs': `You can associate one or more domain names with your application. The primary domain is the one that will be used in Console links and in e-mails sent to you. Several applications can share the same domain, each with a specific subdomain and/or route.`,
'cc-domain-management.form.path.help': () => sanitize`For example: /api or /blog`,
'cc-domain-management.form.path.label': `Route`,
@@ -580,7 +580,7 @@ export const translations = {
'cc-env-var-create.errors.invalid-name': /** @param {{name: string}} _ */ ({ name }) =>
sanitize`Name ${name} is invalid`,
'cc-env-var-create.info.java-prop': /** @param {{name: string}} _ */ ({ name }) =>
- sanitize`Variable ${name} will only be injected as a Java property and won't be part of the environment, more details`,
+ sanitize`Variable ${name} will only be injected as a Java property and won't be part of the environment, more details`,
'cc-env-var-create.name.label': `Variable name`,
'cc-env-var-create.value.label': `Variable value`,
//#endregion
@@ -598,9 +598,9 @@ export const translations = {
'cc-env-var-editor-expert.errors.line': `line`,
'cc-env-var-editor-expert.errors.unknown': `Unknown Error`,
'cc-env-var-editor-expert.example': () =>
- sanitize`Format: VARIABLE_NAME="variable value"
Every variable must be separated by a line break, learn more.`,
+ sanitize`Format: VARIABLE_NAME="variable value"
Every variable must be separated by a line break, learn more.`,
'cc-env-var-editor-expert.info.java-prop': /** @param {{name: string}} _ */ ({ name }) =>
- sanitize`Variable ${name} will only be injected as a Java property and won't be part of the environment, more details`,
+ sanitize`Variable ${name} will only be injected as a Java property and won't be part of the environment, more details`,
'cc-env-var-editor-expert.label': `Variable editing. Format: VARIABLE_NAME="variable value". Every variable must be separated by a line break.`,
//#endregion
//#region cc-env-var-editor-json
@@ -616,9 +616,9 @@ export const translations = {
sanitize`${name} is not a valid variable name in strict mode`,
'cc-env-var-editor-json.errors.unknown': `Unknown Error`,
'cc-env-var-editor-json.example': () =>
- sanitize`Format: { "name": "VARIABLE_NAME", "value": "variable value" }
Array of objects following the above format, learn more.`,
+ sanitize`Format: { "name": "VARIABLE_NAME", "value": "variable value" }
Array of objects following the above format, learn more.`,
'cc-env-var-editor-json.info.java-prop': /** @param {{name: string}} _ */ ({ name }) =>
- sanitize`Variable ${name} will only be injected as a Java property and won't be part of the environment, more details`,
+ sanitize`Variable ${name} will only be injected as a Java property and won't be part of the environment, more details`,
'cc-env-var-editor-json.label': `Variable editing. Array of objects following the format: { "name": "VARIABLE_NAME", "value": "variable value" }.`,
//#endregion
//#region cc-env-var-editor-simple
@@ -633,11 +633,11 @@ export const translations = {
`,
'cc-env-var-form.cli.content.list-var-command': `List environment variables:`,
'cc-env-var-form.description.config-provider': /** @param {{addonName: string}} _ */ ({ addonName }) =>
- sanitize`Configuration exposed to dependent applications. Learn more
These variables will be injected as environment variables in applications that have the add-on ${addonName} in their service dependencies.
Every time you update your changes, all the dependent applications will be automatically restarted.`,
+ sanitize`Configuration exposed to dependent applications. Learn more
These variables will be injected as environment variables in applications that have the add-on ${addonName} in their service dependencies.
Every time you update your changes, all the dependent applications will be automatically restarted.`,
'cc-env-var-form.description.env-var': /** @param {{appName: string}} _ */ ({ appName }) =>
sanitize`These variables will be injected as environment variables in the application ${appName}.`,
'cc-env-var-form.description.exposed-config': /** @param {{appName: string}} _ */ ({ appName }) =>
- sanitize`Configuration exposed to dependent applications. Learn more
These variables won't be injected in the application ${appName}, they will be injected as environment variables in applications that have ${appName} in their service dependencies.`,
+ sanitize`Configuration exposed to dependent applications. Learn more
These variables won't be injected in the application ${appName}, they will be injected as environment variables in applications that have ${appName} in their service dependencies.`,
'cc-env-var-form.documentation.text': `Environment variables - Reference`,
'cc-env-var-form.error.loading': `Something went wrong while loading variables.`,
'cc-env-var-form.heading.config-provider': `Variables`,
@@ -1319,7 +1319,7 @@ export const translations = {
'cc-orga-member-list.invite.email.label': `Email address`,
'cc-orga-member-list.invite.heading': `Invite a member`,
'cc-orga-member-list.invite.info': () =>
- sanitize`More information about roles in the Roles and Organisations page.`,
+ sanitize`More information about roles in the Roles and Organisations page.`,
'cc-orga-member-list.invite.role.accounting': `Accountant`,
'cc-orga-member-list.invite.role.admin': `Admin`,
'cc-orga-member-list.invite.role.developer': `Developer`,
@@ -1831,7 +1831,7 @@ export const translations = {
'cc-token-api-list.empty': `You haven't created any API tokens yet, or none of them are active. Let's create a new one:`,
'cc-token-api-list.error': `Something went wrong while loading API tokens`,
'cc-token-api-list.intro': () =>
- sanitize`Below is the list of API tokens linked to your account and their associated information. You may revoke them as needed.`,
+ sanitize`Below is the list of API tokens linked to your account and their associated information. You may revoke them as needed.`,
'cc-token-api-list.link.doc': `API tokens - Documentation`,
'cc-token-api-list.main-heading': `API tokens`,
'cc-token-api-list.no-password.create-password-btn': `Add a password`,
@@ -1875,7 +1875,7 @@ export const translations = {
'cc-token-oauth-list.empty': `You do not have any third-party applications linked to your account`,
'cc-token-oauth-list.error': `Something went wrong while loading OAuth tokens`,
'cc-token-oauth-list.intro': () =>
- sanitize`Below is the list of third-party applications linked to your account and their associated information. You may revoke these OAuth tokens as needed.`,
+ sanitize`Below is the list of third-party applications linked to your account and their associated information. You may revoke these OAuth tokens as needed.`,
'cc-token-oauth-list.link.doc': `OAuth tokens - Documentation`,
'cc-token-oauth-list.main-heading': `OAuth tokens`,
'cc-token-oauth-list.revoke-all-tokens': `Revoke all OAuth tokens`,
diff --git a/src/translations/translations.fr.js b/src/translations/translations.fr.js
index 8b4afaa39..5642e6fcb 100644
--- a/src/translations/translations.fr.js
+++ b/src/translations/translations.fr.js
@@ -1,3 +1,4 @@
+import { getDevHubUrl, getDocUrl } from '../lib/dev-hub-url.js';
import {
prepareFormatDate,
prepareFormatDateOnly,
@@ -15,7 +16,6 @@ import {
} from '../lib/i18n/i18n-number.js';
import { sanitize } from '../lib/i18n/i18n-sanitize.js';
import { preparePlural } from '../lib/i18n/i18n-string.js';
-import { generateDevHubHref, generateDocsHref } from '../lib/utils.js';
/**
* @typedef {import('../components/common.types.js').Flavor} Flavor
@@ -77,7 +77,7 @@ function formatFlavor(flavor) {
}
const getCliInstructions = () =>
- sanitize`Pour installer les Clever Tools (CLI), suivez les instructions de la documentation.`;
+ sanitize`Pour installer les Clever Tools (CLI), suivez les instructions de la documentation.`;
export const translations = {
//#region cc-addon-admin
@@ -264,7 +264,7 @@ export const translations = {
//#endregion
//#region cc-addon-encryption-at-rest-option
'cc-addon-encryption-at-rest-option.description': () =>
- sanitize`Le chiffrement au repos chiffre l'intégralité du disque de données afin de ne pas y stocker d'informations en clair. Grâce à cette sécurité, l'accès physique au disque empêchera toute lecture des données stockées. Plus d'information dans notre documentation.`,
+ sanitize`Le chiffrement au repos chiffre l'intégralité du disque de données afin de ne pas y stocker d'informations en clair. Grâce à cette sécurité, l'accès physique au disque empêchera toute lecture des données stockées. Plus d'information dans notre documentation.`,
'cc-addon-encryption-at-rest-option.title': `Chiffrement au repos`,
//#endregion
//#region cc-addon-features
@@ -446,12 +446,12 @@ export const translations = {
//#endregion
//#region cc-domain-management
'cc-domain-management.certif.automated': () =>
- sanitize`Que vous utilisiez cleverapps.io ou vos propres noms de domaine avec les applications hébergées par Clever Cloud, un certificat Let's Encrypt est automatiquement généré et renouvelé pour l'accès HTTPS/TLS. Vous n'avez rien à faire. Pour les cas spécifiques, reportez-vous à notre documentation.`,
+ sanitize`Que vous utilisiez cleverapps.io ou vos propres noms de domaine avec les applications hébergées par Clever Cloud, un certificat Let's Encrypt est automatiquement généré et renouvelé pour l'accès HTTPS/TLS. Vous n'avez rien à faire. Pour les cas spécifiques, reportez-vous à notre documentation.`,
'cc-domain-management.certif.custom': () =>
sanitize`Vous pouvez fournir votre propre certificat grâce au gestionnaire de certificats Clever Cloud.`,
'cc-domain-management.certif.heading': `Sécurisez votre application`,
'cc-domain-management.dns.a.desc': () =>
- sanitize`Si vous choisissez d'utiliser des enregistrements de type A, par exemple pour un domaine racine (APEX), vous devrez vous-même assurer leur mise à jour. Pensez à suivre notre changelog ou à lire la documentation de notre API v4 pour cela.
`,
+ sanitize`Si vous choisissez d'utiliser des enregistrements de type A, par exemple pour un domaine racine (APEX), vous devrez vous-même assurer leur mise à jour. Pensez à suivre notre changelog ou à lire la documentation de notre API v4 pour cela.
`,
'cc-domain-management.dns.a.heading': `Enregistrements A`,
'cc-domain-management.dns.a.label': `Valeurs d'enregistrement A`,
'cc-domain-management.dns.cli.content.diag-conf-command': `Commande pour diagnostiquer l'installation actuelle\u00A0:`,
@@ -465,7 +465,7 @@ export const translations = {
'cc-domain-management.dns.documentation.text': `Enregistrements DNS - Documentation`,
'cc-domain-management.dns.heading': `Configurez vos DNS`,
'cc-domain-management.dns.info.desc': () =>
- sanitize`Si vous bénéficiez d'un load balancer dédié, référez-vous à sa configuration ou contactez le support. Notre équipe pourra également vous aider pour commander un tel service. Pour un domaine sans sous-domaine (APEX) ou un sous-domaine avec sa propre zone DNS, référez-vous à notre documentation.`,
+ sanitize`Si vous bénéficiez d'un load balancer dédié, référez-vous à sa configuration ou contactez le support. Notre équipe pourra également vous aider pour commander un tel service. Pour un domaine sans sous-domaine (APEX) ou un sous-domaine avec sa propre zone DNS, référez-vous à notre documentation.`,
'cc-domain-management.dns.info.heading': `Load balancers dédiés et cas spécifiques`,
'cc-domain-management.dns.loading-error': `Une erreur est survenue pendant le chargement des informations DNS`,
'cc-domain-management.form.domain.error.contains-path': /** @param {{path: string}} _ */ ({ path }) =>
@@ -478,7 +478,7 @@ export const translations = {
sanitize`Par exemple\u00A0: example.com, *.example.com ou example.cleverapps.io`,
'cc-domain-management.form.domain.label': `Nom de domaine`,
'cc-domain-management.form.info.cleverapps': () =>
- sanitize`Par défaut, une application se voit attribuer un nom de domaine de type app_id.cleverapps.io. Vous pouvez le supprimer ou changer le sous-domaine librement, mais xxx.cleverapps.io doit uniquement être utilisé à des fins de test (voir notre documentation).`,
+ sanitize`Par défaut, une application se voit attribuer un nom de domaine de type app_id.cleverapps.io. Vous pouvez le supprimer ou changer le sous-domaine librement, mais xxx.cleverapps.io doit uniquement être utilisé à des fins de test (voir notre documentation).`,
'cc-domain-management.form.info.docs': `Vous pouvez associer un ou plusieurs noms de domaines à votre application. Le domaine principal sera utilisé dans les liens de la Console et dans les e-mails qui vous seront envoyés. Plusieurs applications peuvent partager un même domaine, chacune avec un sous-domaine et/ou une route spécifique.`,
'cc-domain-management.form.path.help': () => sanitize`Par exemple\u00A0: /api ou /blog`,
'cc-domain-management.form.path.label': `Route`,
@@ -590,7 +590,7 @@ export const translations = {
'cc-env-var-create.errors.invalid-name': /** @param {{name: string}} _ */ ({ name }) =>
sanitize`Le nom ${name} n'est pas valide`,
'cc-env-var-create.info.java-prop': /** @param {{name: string}} _ */ ({ name }) =>
- sanitize`La variable ${name} sera injecté sous forme de propriété Java et non en tant que variable d'environnement, plus de détails`,
+ sanitize`La variable ${name} sera injecté sous forme de propriété Java et non en tant que variable d'environnement, plus de détails`,
'cc-env-var-create.name.label': `Nom de la variable`,
'cc-env-var-create.value.label': `Valeur de la variable`,
//#endregion
@@ -608,9 +608,9 @@ export const translations = {
'cc-env-var-editor-expert.errors.line': `ligne`,
'cc-env-var-editor-expert.errors.unknown': `Erreur inconnue`,
'cc-env-var-editor-expert.example': () =>
- sanitize`Format\u00A0: NOM_DE_LA_VARIABLE="valeur de la variable"
Chaque variable doit être séparée par des sauts de ligne, en savoir plus.`,
+ sanitize`Format\u00A0: NOM_DE_LA_VARIABLE="valeur de la variable"
Chaque variable doit être séparée par des sauts de ligne, en savoir plus.`,
'cc-env-var-editor-expert.info.java-prop': /** @param {{name: string}} _ */ ({ name }) =>
- sanitize`La variable ${name} sera injecté sous forme de propriété Java et non en tant que variable d'environnement, plus de détails`,
+ sanitize`La variable ${name} sera injecté sous forme de propriété Java et non en tant que variable d'environnement, plus de détails`,
'cc-env-var-editor-expert.label': `Edition des variables. Format\u00A0: NOM_DE_LA_VARIABLE="valeur de la variable". Chaque variable doit être séparée par des sauts de ligne.`,
//#endregion
//#region cc-env-var-editor-json
@@ -625,9 +625,9 @@ export const translations = {
sanitize`Le nom ${name} n'est pas valide en mode strict`,
'cc-env-var-editor-json.errors.unknown': `Erreur inconnue`,
'cc-env-var-editor-json.example': () =>
- sanitize`Format\u00A0: { "name": "NOM_DE_LA_VARIABLE", "value": "valeur de la variable" }
Tableau d'objets respectant le format ci-dessus, en savoir plus.`,
+ sanitize`Format\u00A0: { "name": "NOM_DE_LA_VARIABLE", "value": "valeur de la variable" }
Tableau d'objets respectant le format ci-dessus, en savoir plus.`,
'cc-env-var-editor-json.info.java-prop': /** @param {{name: string}} _ */ ({ name }) =>
- sanitize`La variable ${name} sera injecté sous forme de propriété Java et non en tant que variable d'environnement, plus de détails`,
+ sanitize`La variable ${name} sera injecté sous forme de propriété Java et non en tant que variable d'environnement, plus de détails`,
'cc-env-var-editor-json.label': `Edition des variables. Tableau d'objets respectant le format\u00A0: { "name": "NOM_DE_LA_VARIABLE", "value": "valeur de la variable" }.`,
//#endregion
//#region cc-env-var-editor-simple
@@ -642,11 +642,11 @@ export const translations = {
`,
'cc-env-var-form.cli.content.list-var-command': `Lister les variables d'environnement\u00A0:`,
'cc-env-var-form.description.config-provider': /** @param {{addonName: string}} _ */ ({ addonName }) =>
- sanitize`Configuration publiée pour les applications dépendantes. En savoir plus
Ces seront injectées en tant que variables d'environnement dans les applications qui ont l'add-on ${addonName} dans leurs services liés.
À chaque fois que vous mettez à jour les changements, toutes les applications dépendantes seront redémarrées automatiquement.`,
+ sanitize`Configuration publiée pour les applications dépendantes. En savoir plus
Ces seront injectées en tant que variables d'environnement dans les applications qui ont l'add-on ${addonName} dans leurs services liés.
À chaque fois que vous mettez à jour les changements, toutes les applications dépendantes seront redémarrées automatiquement.`,
'cc-env-var-form.description.env-var': /** @param {{appName: string}} _ */ ({ appName }) =>
sanitize`Ces variables seront injectées en tant que variables d'environnement dans l'application ${appName}.`,
'cc-env-var-form.description.exposed-config': /** @param {{appName: string}} _ */ ({ appName }) =>
- sanitize`Configuration publiée pour les applications dépendantes. En savoir plus
Ces variables ne seront pas injectées dans l'application ${appName}, elles seront injectées en tant que variables d'environnement dans les applications qui ont ${appName} dans leurs services liés.`,
+ sanitize`Configuration publiée pour les applications dépendantes. En savoir plus
Ces variables ne seront pas injectées dans l'application ${appName}, elles seront injectées en tant que variables d'environnement dans les applications qui ont ${appName} dans leurs services liés.`,
'cc-env-var-form.documentation.text': `Variables d’environnement - Référence`,
'cc-env-var-form.error.loading': `Une erreur est survenue pendant le chargement des variables.`,
'cc-env-var-form.heading.config-provider': `Variables`,
@@ -1332,7 +1332,7 @@ export const translations = {
'cc-orga-member-list.invite.email.label': `Adresse e-mail`,
'cc-orga-member-list.invite.heading': `Inviter un membre`,
'cc-orga-member-list.invite.info': () =>
- sanitize`Plus d'informations à propos des rôles sur la page Rôles et organisations (en anglais)`,
+ sanitize`Plus d'informations à propos des rôles sur la page Rôles et organisations (en anglais)`,
'cc-orga-member-list.invite.role.accounting': `Comptable`,
'cc-orga-member-list.invite.role.admin': `Admin`,
'cc-orga-member-list.invite.role.developer': `Développeur`,
@@ -1858,7 +1858,7 @@ export const translations = {
'cc-token-api-list.empty': `Vous n'avez aucun token d'API, ou aucun d'eux n'est actif. Créez un nouveau token\u00A0:`,
'cc-token-api-list.error': `Une erreur est survenue pendant le chargement des tokens d'API`,
'cc-token-api-list.intro': () =>
- sanitize`Ci-dessous la liste des tokens d'API associés à votre compte et leurs informations. Vous pouvez les révoquer si nécessaire.`,
+ sanitize`Ci-dessous la liste des tokens d'API associés à votre compte et leurs informations. Vous pouvez les révoquer si nécessaire.`,
'cc-token-api-list.link.doc': `Tokens d'API - Documentation`,
'cc-token-api-list.main-heading': `Tokens d'API`,
'cc-token-api-list.no-password.create-password-btn': `Ajouter un mot de passe`,
@@ -1903,7 +1903,7 @@ export const translations = {
'cc-token-oauth-list.empty': `Aucune application tierce n'est liée à votre compte`,
'cc-token-oauth-list.error': `Une erreur est survenue pendant le chargement des tokens OAuth`,
'cc-token-oauth-list.intro': () =>
- sanitize`Ci-dessous la liste des applications tierces liées à votre compte et leurs informations. Vous pouvez révoquer leurs tokens OAuth si vous le souhaitez.`,
+ sanitize`Ci-dessous la liste des applications tierces liées à votre compte et leurs informations. Vous pouvez révoquer leurs tokens OAuth si vous le souhaitez.`,
'cc-token-oauth-list.link.doc': `Tokens OAuth - Documentation`,
'cc-token-oauth-list.main-heading': `Tokens OAuth`,
'cc-token-oauth-list.revoke-all-tokens': `Révoquer tous les tokens OAuth`,
diff --git a/test/assets-url.test.js b/test/resource-urls.test.js
similarity index 51%
rename from test/assets-url.test.js
rename to test/resource-urls.test.js
index 09f85fbd8..ca32bed5d 100644
--- a/test/assets-url.test.js
+++ b/test/resource-urls.test.js
@@ -1,5 +1,6 @@
import { expect } from '@bundled-es-modules/chai';
import { getAssetUrl, setAssetsBaseUrl } from '../src/lib/assets-url.js';
+import { getDevHubUrl, getDocUrl, setDevHubBaseUrl } from '../src/lib/dev-hub-url.js';
describe('assets-url module', () => {
describe('getAssetUrl function', () => {
@@ -72,3 +73,76 @@ describe('assets-url module', () => {
});
});
});
+
+describe('dev-hub-url module', () => {
+ describe('getDocUrl', () => {
+ it('should append /doc/ and path to base URL without trailing slash', () => {
+ expect(getDocUrl('foo/bar')).to.equal('https://www.clever.cloud/developers/doc/foo/bar');
+ });
+
+ it('should handle path with leading slash', () => {
+ expect(getDocUrl('/foo/bar')).to.equal('https://www.clever.cloud/developers/doc/foo/bar');
+ });
+
+ it('should handle empty path', () => {
+ expect(getDocUrl()).to.equal('https://www.clever.cloud/developers/doc');
+ });
+ });
+
+ describe('getDevHubUrl', () => {
+ it('should append path to base URL without trailing slash', () => {
+ expect(getDevHubUrl('foo/bar')).to.equal('https://www.clever.cloud/developers/foo/bar');
+ });
+
+ it('should handle path with leading slash', () => {
+ expect(getDevHubUrl('/foo/bar')).to.equal('https://www.clever.cloud/developers/foo/bar');
+ });
+
+ it('should handle empty path', () => {
+ expect(getDevHubUrl()).to.equal('https://www.clever.cloud/developers');
+ });
+ });
+
+ describe('setDevHubBaseUrl', () => {
+ const originalUrl = 'https://www.clever.cloud/developers';
+
+ afterEach(() => {
+ setDevHubBaseUrl(originalUrl);
+ });
+
+ it('should set custom base URL without trailing slash', () => {
+ setDevHubBaseUrl('https://custom-dev-hub.example.com');
+ expect(getDevHubUrl('foo/bar')).to.equal('https://custom-dev-hub.example.com/foo/bar');
+ });
+
+ it('should set custom base URL with trailing slash', () => {
+ setDevHubBaseUrl('https://custom-dev-hub.example.com/');
+ expect(getDevHubUrl('foo/bar')).to.equal('https://custom-dev-hub.example.com/foo/bar');
+ });
+
+ it('should affect getDocUrl', () => {
+ setDevHubBaseUrl('https://custom-dev-hub.example.com');
+ expect(getDocUrl('foo/bar')).to.equal('https://custom-dev-hub.example.com/doc/foo/bar');
+ });
+
+ it('should work with localhost URL', () => {
+ setDevHubBaseUrl('http://localhost:8080');
+ expect(getDevHubUrl('foo/bar')).to.equal('http://localhost:8080/foo/bar');
+ expect(getDocUrl('foo/bar')).to.equal('http://localhost:8080/doc/foo/bar');
+ });
+
+ it('should work with relative path base URL', () => {
+ setDevHubBaseUrl('/local/dev-hub');
+ expect(getDevHubUrl('foo/bar')).to.equal('/local/dev-hub/foo/bar');
+ expect(getDocUrl('foo/bar')).to.equal('/local/dev-hub/doc/foo/bar');
+ });
+
+ it('should reset to new URL after multiple calls', () => {
+ setDevHubBaseUrl('https://first-hub.example.com');
+ expect(getDevHubUrl('test')).to.equal('https://first-hub.example.com/test');
+
+ setDevHubBaseUrl('https://second-hub.example.com/');
+ expect(getDevHubUrl('test')).to.equal('https://second-hub.example.com/test');
+ });
+ });
+});
diff --git a/test/utils.test.js b/test/utils.test.js
index 69143c9a3..c2febf9c2 100644
--- a/test/utils.test.js
+++ b/test/utils.test.js
@@ -1,14 +1,5 @@
import { expect } from '@bundled-es-modules/chai';
-import {
- clampNumber,
- generateDevHubHref,
- generateDocsHref,
- groupBy,
- isStringBlank,
- isStringEmpty,
- randomString,
- range,
-} from '../src/lib/utils.js';
+import { clampNumber, groupBy, isStringBlank, isStringEmpty, randomString, range } from '../src/lib/utils.js';
describe('range function', function () {
it('should return array', function () {
@@ -135,31 +126,3 @@ describe('groupBy function', () => {
expect(grouped).to.eql({});
});
});
-
-describe('generateDocsHref', () => {
- it('should append /doc/ and path to base URL without trailing slash', () => {
- expect(generateDocsHref('foo/bar')).to.equal('https://www.clever.cloud/developers/doc/foo/bar');
- });
-
- it('should handle path with leading slash', () => {
- expect(generateDocsHref('/foo/bar')).to.equal('https://www.clever.cloud/developers/doc/foo/bar');
- });
-
- it('should handle empty path', () => {
- expect(generateDocsHref()).to.equal('https://www.clever.cloud/developers/doc/');
- });
-});
-
-describe('generateDevHubHref', () => {
- it('should append path to base URL without trailing slash', () => {
- expect(generateDevHubHref('foo/bar')).to.equal('https://www.clever.cloud/developers/foo/bar');
- });
-
- it('should handle path with leading slash', () => {
- expect(generateDevHubHref('/foo/bar')).to.equal('https://www.clever.cloud/developers/foo/bar');
- });
-
- it('should handle empty path', () => {
- expect(generateDevHubHref()).to.equal('https://www.clever.cloud/developers/');
- });
-});