Rebrand Boundary UI to Threshold; neutralize HashiCorp update/phone-home endpoints
- Product name, app id, installer names, translations, titles -> Threshold - Desktop: look for threshold CLI first, then boundary; migrate legacy Boundary user-data dir; updater/release lookups disabled unless configured - Remove HashiCorp/IBM logos and art (plain placeholder mark), GitHub release workflows and HashiCorp release metadata - Add NOTICE and README 'About this fork' Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
No files matched your search
@@ -3,7 +3,7 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<SettingsCard @header={{t 'settings.application'}} @icon='boundary'>
|
||||
<SettingsCard @header={{t 'settings.application'}} @icon='shield'>
|
||||
<:body>
|
||||
<div>
|
||||
<Hds::Text::Display>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { TrackedArray } from 'tracked-built-ins';
|
||||
import config from 'desktop/config/environment';
|
||||
|
||||
export default class ScopesRoute extends Route {
|
||||
// =services
|
||||
@@ -62,12 +63,16 @@ export default class ScopesRoute extends Route {
|
||||
let downloadError = false;
|
||||
|
||||
if (!this.isPaginationSupported) {
|
||||
const metaDataUrl =
|
||||
'https://api.releases.hashicorp.com/v1/releases/boundary-desktop/1.7.1';
|
||||
// Threshold has no default release metadata API (the upstream
|
||||
// HashiCorp releases API is intentionally not contacted). When
|
||||
// `config.releases.metadataUrl` is empty we skip the request and direct
|
||||
// the user to the releases page instead.
|
||||
const metaDataUrl = config.releases?.metadataUrl;
|
||||
const { isWindows, isMac, isLinux } =
|
||||
await window.desktop.system.checkOS();
|
||||
|
||||
try {
|
||||
if (!metaDataUrl) throw new Error('No release metadata URL');
|
||||
const metaDataResponse = await fetch(metaDataUrl);
|
||||
const metaData = await metaDataResponse.json();
|
||||
|
||||
@@ -87,6 +92,7 @@ export default class ScopesRoute extends Route {
|
||||
}
|
||||
}
|
||||
|
||||
controller.set('releasesPageUrl', config.releases?.releasesPageUrl);
|
||||
controller.set('downloadLink', downloadLink);
|
||||
controller.set('downloadError', downloadError);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
>
|
||||
<:logo>
|
||||
<Hds::AppHeader::HomeLink
|
||||
@icon='boundary'
|
||||
@color='var(--token-color-boundary-brand)'
|
||||
@icon='shield'
|
||||
@color='var(--token-color-foreground-primary)'
|
||||
@text={{(app-name)}}
|
||||
@route='index'
|
||||
class='app-header__logo home-link-color-override'
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<A.Button
|
||||
@text={{t 'unsupported-controller.buttons.releases-page'}}
|
||||
@color='secondary'
|
||||
@href='https://releases.hashicorp.com/boundary-desktop/'
|
||||
@href={{this.releasesPageUrl}}
|
||||
@isHrefExternal={{true}}
|
||||
data-test-releases-link
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const APP_NAME = process.env.APP_NAME || 'Boundary';
|
||||
const APP_NAME = process.env.APP_NAME || 'Threshold';
|
||||
const locationType = process.env.EMBER_CLI_ELECTRON ? 'hash' : 'history';
|
||||
const ENABLE_A11Y_AUDIT = process.env.ENABLE_A11Y_AUDIT || false;
|
||||
const COLOR_THEME = process.env.COLOR_THEME ?? 'light';
|
||||
@@ -41,6 +41,16 @@ module.exports = function (environment) {
|
||||
|
||||
appName: APP_NAME,
|
||||
|
||||
// Threshold has no default release metadata API; set `metadataUrl` to a
|
||||
// HashiCorp-releases-API-compatible endpoint to enable direct download links
|
||||
// when connecting to an older controller. See README "About this fork".
|
||||
releases: {
|
||||
metadataUrl: process.env.THRESHOLD_RELEASES_METADATA_URL || '',
|
||||
releasesPageUrl:
|
||||
process.env.THRESHOLD_RELEASES_PAGE_URL ||
|
||||
'https://repo.anhonesthost.net/CyberCoveLLC/threshold-ui/releases',
|
||||
},
|
||||
|
||||
oidcPollingTimeoutSeconds: 1,
|
||||
sessionPollingTimeoutSeconds: 2,
|
||||
|
||||
@@ -51,7 +61,8 @@ module.exports = function (environment) {
|
||||
'/tutorials/oss-getting-started/oss-getting-started-desktop-app#connect',
|
||||
sessions:
|
||||
'/tutorials/oss-getting-started/oss-getting-started-desktop-app#connect',
|
||||
'support-page': 'https://support.hashicorp.com/hc/en-us',
|
||||
'support-page':
|
||||
'https://repo.anhonesthost.net/CyberCoveLLC/threshold-ui/issues',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -104,6 +115,10 @@ module.exports = function (environment) {
|
||||
|
||||
ENV.enableConfirmService = false;
|
||||
|
||||
// Use a non-routable host for mocked release metadata.
|
||||
ENV.releases.metadataUrl = 'https://releases-api.threshold.invalid/v1/meta';
|
||||
ENV.releases.releasesPageUrl = 'https://releases.threshold.invalid/';
|
||||
|
||||
// Use in-memory storage
|
||||
ENV.storage = {
|
||||
memory: true,
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 334 B |
@@ -39,10 +39,24 @@ const downloadArtifact = (version) => {
|
||||
`Download cli for platform: ${archivePlatform.name} arch: ${archivePlatform.arch}`,
|
||||
);
|
||||
|
||||
const archiveName = `boundary_${version}_${archivePlatform.name}_${archivePlatform.arch}.zip`;
|
||||
const url = `https://releases.hashicorp.com/boundary/${version}/${archiveName}`;
|
||||
// Threshold does not default to any download host. The CLI release base URL
|
||||
// must be provided (layout: `<base>/<version>/<prefix>_<version>_<os>_<arch>.zip`).
|
||||
const baseUrl = (process.env.THRESHOLD_CLI_RELEASES_URL || '').replace(
|
||||
/\/+$/,
|
||||
'',
|
||||
);
|
||||
if (!baseUrl) {
|
||||
throw new Error(
|
||||
'THRESHOLD_CLI_RELEASES_URL must be set when SETUP_CLI=true (no default download host is configured).',
|
||||
);
|
||||
}
|
||||
const archivePrefix = process.env.THRESHOLD_CLI_ARCHIVE_PREFIX || 'threshold';
|
||||
const archiveName = `${archivePrefix}_${version}_${archivePlatform.name}_${archivePlatform.arch}.zip`;
|
||||
const url = `${baseUrl}/${version}/${archiveName}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'boundary-artifact-'));
|
||||
const tmpDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'threshold-artifact-'),
|
||||
);
|
||||
console.log('Create tmp artifact directory: ', tmpDir);
|
||||
|
||||
const artifactFileName = url.split('/').pop();
|
||||
|
||||
@@ -19,12 +19,16 @@ const returnSemVerFromReleaseVersion = (releaseVersion) => {
|
||||
// Create config
|
||||
const createConfig = () => {
|
||||
const config = {
|
||||
name: 'boundary',
|
||||
productName: 'Boundary',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} HashiCorp, Inc.`,
|
||||
name: 'threshold',
|
||||
productName: 'Threshold',
|
||||
// Upstream copyright is retained; see NOTICE and LICENSE in the repository root.
|
||||
copyright: `Copyright © 2021-2026 HashiCorp, Inc. / IBM Corp. (upstream Boundary UI, BUSL-1.1). Threshold fork by CyberCove LLC.`,
|
||||
releaseVersion: process.env.RELEASE_VERSION,
|
||||
releaseCommit: process.env.RELEASE_COMMIT,
|
||||
executableName: 'Boundary',
|
||||
executableName: 'Threshold',
|
||||
// Update feed (directory listing in the releases.hashicorp.com layout).
|
||||
// Empty disables the in-app updater and the update-related network access.
|
||||
updateFeedUrl: process.env.THRESHOLD_UPDATE_FEED_URL || '',
|
||||
releaseVersionRaw: process.env.RELEASE_VERSION,
|
||||
};
|
||||
|
||||
@@ -38,7 +42,7 @@ const createConfig = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (isLinux()) config.executableName = 'boundary-desktop';
|
||||
if (isLinux()) config.executableName = 'threshold-desktop';
|
||||
return config;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Generated during build
|
||||
const config = require('./config.js');
|
||||
const { isMac } = require('../src/helpers/platform.js');
|
||||
|
||||
// THRESHOLD_* is preferred; the upstream BOUNDARY_* name remains supported.
|
||||
const signingIdentity =
|
||||
process.env.THRESHOLD_DESKTOP_SIGNING_IDENTITY ||
|
||||
process.env.BOUNDARY_DESKTOP_SIGNING_IDENTITY;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
@@ -17,11 +22,11 @@ module.exports = {
|
||||
icon: './assets/app-icons/icon',
|
||||
name: config.productName,
|
||||
appVersion: config.releaseVersion,
|
||||
appBundleId: 'com.electron.boundary',
|
||||
appBundleId: 'net.anhonesthost.cybercove.threshold',
|
||||
appCopyright: config.copyright,
|
||||
executableName: config.executableName,
|
||||
osxSign: {
|
||||
identity: process.env.BOUNDARY_DESKTOP_SIGNING_IDENTITY,
|
||||
identity: signingIdentity,
|
||||
'hardened-runtime': true,
|
||||
entitlements: './assets/macos/entitlements.plist',
|
||||
'entitlements-inherit': './assets/macos/entitlements.plist',
|
||||
@@ -39,7 +44,7 @@ module.exports = {
|
||||
},
|
||||
beforeAsar: [
|
||||
async (buildPath, electronVersion, platform, arch, done) => {
|
||||
// Delete the boundary CLI before we build the ASAR as it gets copied outside
|
||||
// Delete the bundled CLI before we build the ASAR as it gets copied outside
|
||||
// in the resources folder
|
||||
const cliPath = path.join(buildPath, 'cli');
|
||||
await fs.promises.rm(cliPath, { recursive: true, force: true });
|
||||
@@ -68,11 +73,11 @@ module.exports = {
|
||||
name: '@electron-forge/maker-deb',
|
||||
config: {
|
||||
options: {
|
||||
name: 'boundary-desktop',
|
||||
name: 'threshold-desktop',
|
||||
version: config.releaseVersion,
|
||||
icon: './assets/app-icons/icon.png',
|
||||
description: 'Desktop Client for Boundary',
|
||||
productDescription: 'Desktop Client for Boundary',
|
||||
description: 'Desktop Client for Threshold',
|
||||
productDescription: 'Desktop Client for Threshold',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -83,7 +88,7 @@ module.exports = {
|
||||
console.log(`[package] Release version: ${config.releaseVersion}`);
|
||||
|
||||
// Check for MacOS signing identity.
|
||||
if (isMac() && !process.env.BOUNDARY_DESKTOP_SIGNING_IDENTITY)
|
||||
if (isMac() && !signingIdentity)
|
||||
console.warn(
|
||||
'[package] WARNING: Could not find signing identity. Proceeding without signing.',
|
||||
);
|
||||
@@ -116,11 +121,11 @@ module.exports = {
|
||||
}
|
||||
|
||||
if (platform === 'linux' && artifact.endsWith('.deb')) {
|
||||
name = `boundary-desktop_${version}_${arch}${path.extname(
|
||||
name = `threshold-desktop_${version}_${arch}${path.extname(
|
||||
artifact,
|
||||
)}`;
|
||||
} else {
|
||||
name = `boundary-desktop_${version}_${platform}_${arch}${path.extname(
|
||||
name = `threshold-desktop_${version}_${platform}_${arch}${path.extname(
|
||||
artifact,
|
||||
)}`;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "boundary-desktop",
|
||||
"productName": "Boundary",
|
||||
"name": "threshold-desktop",
|
||||
"productName": "Threshold",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Electron UI for client for Boundary.",
|
||||
"description": "Electron desktop client for Threshold.",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "node -e \"require('./config/cli.js').setup()\"",
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
|
||||
const { spawnSync } = require('../helpers/spawn-promise.js');
|
||||
const { path, isBuiltInCli } = require('./path.js');
|
||||
const { resolveCliPath, isBuiltInCli } = require('./path.js');
|
||||
|
||||
module.exports = {
|
||||
// Check boundary cli existence
|
||||
exists: () => Boolean(path),
|
||||
// Check threshold (or boundary) cli existence
|
||||
exists: () => Boolean(resolveCliPath()),
|
||||
|
||||
isBuiltInCli,
|
||||
|
||||
|
||||
@@ -9,31 +9,58 @@ const isDev = require('electron-is-dev');
|
||||
const { existsSync } = require('node:fs');
|
||||
const which = require('which');
|
||||
|
||||
const binaryName = isWindows() ? 'boundary.exe' : 'boundary';
|
||||
const builtInCliPath = isDev
|
||||
? path.resolve(__dirname, '..', '..', 'cli', binaryName)
|
||||
: path.resolve(process.resourcesPath, 'cli', binaryName);
|
||||
// Candidate CLI binary names, in order of preference. Threshold ships a
|
||||
// `threshold` binary; the server package also ships a `boundary` alias, and
|
||||
// upstream Boundary installs are still supported.
|
||||
const cliNames = ['threshold', 'boundary'];
|
||||
const exe = (name) => (isWindows() ? `${name}.exe` : name);
|
||||
|
||||
const builtInDir = isDev
|
||||
? path.resolve(__dirname, '..', '..', 'cli')
|
||||
: path.resolve(process.resourcesPath, 'cli');
|
||||
|
||||
// Built-in CLI (bundled in the app resources), first match wins.
|
||||
const builtInCliPath = cliNames
|
||||
.map((name) => path.resolve(builtInDir, exe(name)))
|
||||
.find((candidate) => existsSync(candidate));
|
||||
|
||||
// Return true if the CLI in usage is the built in. False if relies on system CLI.
|
||||
const isBuiltInCli = existsSync(builtInCliPath);
|
||||
const isBuiltInCli = Boolean(builtInCliPath);
|
||||
|
||||
let cachedSystemCli;
|
||||
|
||||
/**
|
||||
* Returns Boundary CLI path if the CLI is built in or the Boundary binary name
|
||||
* if not, so we assume boundary is available within user $PATH.
|
||||
* Looks up the first available CLI name in the user's $PATH. On Windows we
|
||||
* also filter out any path within the current working directory because spawn
|
||||
* methods may not always find the correct binary path.
|
||||
* Resolved lazily (the app fixes up $PATH after this module is first loaded)
|
||||
* and cached once found. Falls back to the preferred name so spawn errors are
|
||||
* meaningful.
|
||||
*/
|
||||
const pathBoundary = isBuiltInCli
|
||||
? builtInCliPath
|
||||
: // On Windows, we need to filter out the path that is within the current working directory.
|
||||
// This is necessary because the spawn methods may not always find the correct binary path.
|
||||
// The `which` module returns an array of all available paths for the 'boundary' binary.
|
||||
// We then filter out the path that is within the current working directory.
|
||||
isWindows()
|
||||
? which
|
||||
.sync(binaryName, { nothrow: true, all: true })
|
||||
.filter((binary) => !binary.startsWith(process.cwd()))[0]
|
||||
: binaryName;
|
||||
const findSystemCli = () => {
|
||||
if (cachedSystemCli) return cachedSystemCli;
|
||||
for (const name of cliNames) {
|
||||
const found = which
|
||||
.sync(exe(name), { nothrow: true, all: true })
|
||||
?.filter((binary) => !isWindows() || !binary.startsWith(process.cwd()));
|
||||
if (found && found.length) {
|
||||
cachedSystemCli = isWindows() ? found[0] : exe(name);
|
||||
return cachedSystemCli;
|
||||
}
|
||||
}
|
||||
return isWindows() ? undefined : exe(cliNames[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the CLI path if the CLI is built in, otherwise the name (or path)
|
||||
* of the first of `threshold` / `boundary` found within user $PATH.
|
||||
*/
|
||||
const resolveCliPath = () => (isBuiltInCli ? builtInCliPath : findSystemCli());
|
||||
|
||||
module.exports = {
|
||||
path: pathBoundary,
|
||||
resolveCliPath,
|
||||
get path() {
|
||||
return resolveCliPath();
|
||||
},
|
||||
isBuiltInCli,
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
const runtimeSettings = require('../services/runtime-settings.js');
|
||||
const isDev = require('electron-is-dev');
|
||||
const appUpdater = require('../helpers/app-updater.js');
|
||||
|
||||
const csp = {
|
||||
'default-src': ["'none'"],
|
||||
@@ -39,9 +40,13 @@ const generateCSPHeader = () => {
|
||||
policy['connect-src'].push(runtimeSettings.clusterUrl);
|
||||
}
|
||||
|
||||
// add hashicorp release endpoints to connect-src
|
||||
policy['connect-src'].push('https://api.releases.hashicorp.com');
|
||||
policy['connect-src'].push('https://releases.hashicorp.com');
|
||||
// Allow the configured update feed origin (if any) in connect-src. There is
|
||||
// no default feed; the in-app updater is disabled unless one is configured.
|
||||
const updateFeedOrigin = appUpdater.feedOrigin();
|
||||
if (updateFeedOrigin) {
|
||||
policy['connect-src'] = policy['connect-src'].slice();
|
||||
policy['connect-src'].push(updateFeedOrigin);
|
||||
}
|
||||
|
||||
return Object.keys(policy)
|
||||
.map((key) => `${key} ${policy[key].join(' ')};`)
|
||||
|
||||
@@ -23,14 +23,14 @@ const generateMenuTemplate = () => {
|
||||
...(isMac()
|
||||
? [
|
||||
{
|
||||
label: 'Boundary',
|
||||
label: 'Threshold',
|
||||
submenu: [
|
||||
// { role: 'about' },
|
||||
{
|
||||
label: `About ${config.productName}`,
|
||||
click: aboutDialog,
|
||||
},
|
||||
...(isBuiltInCli
|
||||
...(isBuiltInCli && appUpdater.isEnabled()
|
||||
? [
|
||||
{
|
||||
id: 'update',
|
||||
@@ -115,7 +115,10 @@ const generateMenuTemplate = () => {
|
||||
submenu: [
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: () => shell.openExternal('https://www.boundaryproject.io'),
|
||||
click: () =>
|
||||
shell.openExternal(
|
||||
'https://repo.anhonesthost.net/CyberCoveLLC/threshold-ui',
|
||||
),
|
||||
},
|
||||
...(isWindows()
|
||||
? [
|
||||
|
||||
@@ -16,7 +16,23 @@ const boundaryCli = require('../cli/index.js');
|
||||
|
||||
const currentVersion = config.releaseVersion;
|
||||
const debug = process.env.DEBUG_APP_UPDATER;
|
||||
const releasesUrl = 'https://releases.hashicorp.com/boundary-desktop/';
|
||||
// Threshold has no default update feed. The updater is only active when a feed
|
||||
// URL is configured at build time (THRESHOLD_UPDATE_FEED_URL, see
|
||||
// config/desktop.js). The feed is expected to be a directory listing that
|
||||
// follows the upstream releases layout (`<feed>/<version>/threshold-desktop_<version>_darwin_<arch>.zip`).
|
||||
const feedUrl = (config.updateFeedUrl || '').trim();
|
||||
const releasesUrl = feedUrl ? feedUrl.replace(/\/?$/, '/') : '';
|
||||
|
||||
const isEnabled = () => Boolean(releasesUrl);
|
||||
|
||||
// Origin of the feed for use in the content security policy.
|
||||
const feedOrigin = () => {
|
||||
try {
|
||||
return releasesUrl ? new URL(releasesUrl).origin : '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// Returns the real CPU architecture of the machine.
|
||||
const returnArchitectureToUpdate = () => {
|
||||
@@ -56,7 +72,7 @@ const findLatestVersion = (url) => {
|
||||
// Find zip archive for update
|
||||
const findUpdateArchive = (version) => {
|
||||
const architecture = returnArchitectureToUpdate();
|
||||
const url = `${releasesUrl}${version}/boundary-desktop_${version}_darwin_${architecture}.zip`;
|
||||
const url = `${releasesUrl}${version}/threshold-desktop_${version}_darwin_${architecture}.zip`;
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, (response) => {
|
||||
if (response.statusCode === 200) {
|
||||
@@ -156,12 +172,19 @@ const displayDownloadPrompt = (latestVersion, url) => {
|
||||
* TODO: download progress in dialog
|
||||
**/
|
||||
module.exports = {
|
||||
isEnabled,
|
||||
feedOrigin,
|
||||
run: async ({ suppressNoUpdatePrompt } = {}) => {
|
||||
/**
|
||||
* Ignore app updater prompts.
|
||||
*/
|
||||
if (process.env.BYPASS_APP_UPDATER) return;
|
||||
|
||||
/**
|
||||
* No update feed configured: never contact any server.
|
||||
*/
|
||||
if (!isEnabled()) return;
|
||||
|
||||
/**
|
||||
* Disable app updater check for linux as update is unsupported.
|
||||
* TODO: Enable for windows pending feature dev. Windows is supported.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Threshold fork: the application name changed from "Boundary" to "Threshold",
|
||||
* which changes Electron's default `userData` directory. To avoid losing a
|
||||
* user's persisted settings (cluster URL, log level, preferred RDP client,
|
||||
* stored session partition) we copy the legacy directory once, the first time
|
||||
* the new directory does not exist yet. The legacy directory is left untouched.
|
||||
*
|
||||
* This must run before any module that instantiates `electron-store`.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { app } = require('electron');
|
||||
|
||||
const LEGACY_APP_NAMES = ['Boundary'];
|
||||
|
||||
// Minimal recursive copy (regular files and directories only).
|
||||
const copyDir = (source, destination) => {
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
||||
const from = path.join(source, entry.name);
|
||||
const to = path.join(destination, entry.name);
|
||||
if (entry.isDirectory()) copyDir(from, to);
|
||||
else if (entry.isFile()) fs.copyFileSync(from, to);
|
||||
}
|
||||
};
|
||||
|
||||
const migrate = () => {
|
||||
try {
|
||||
const userData = app.getPath('userData');
|
||||
if (fs.existsSync(userData)) return;
|
||||
|
||||
const appData = path.dirname(userData);
|
||||
for (const legacyName of LEGACY_APP_NAMES) {
|
||||
const legacyDir = path.join(appData, legacyName);
|
||||
if (
|
||||
legacyDir !== userData &&
|
||||
fs.existsSync(path.join(legacyDir, 'config.json'))
|
||||
) {
|
||||
copyDir(legacyDir, userData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Never block startup because of a failed best-effort migration.
|
||||
console.warn('Could not migrate legacy user data:', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
migrate();
|
||||
|
||||
module.exports = { migrate };
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
const { path: boundaryPath } = require('../cli/path.js');
|
||||
const { resolveCliPath } = require('../cli/path.js');
|
||||
const { spawn, spawnSync } = require('child_process');
|
||||
|
||||
// You can throw exceptions, or allow them to occur, and this is supported.
|
||||
@@ -28,7 +28,7 @@ module.exports = {
|
||||
* @param {object} envVars
|
||||
* @param {string} path
|
||||
* @returns {{stdout: string | undefined, stderr: string | undefined}} */
|
||||
spawnSync(args, envVars = {}, path = boundaryPath) {
|
||||
spawnSync(args, envVars = {}, path = resolveCliPath()) {
|
||||
const childProcess = spawnSync(path, args, {
|
||||
// Some of our outputs (namely cache daemon searching) can be very large.
|
||||
// This an undocumented hack to allow for an unlimited buffer size which
|
||||
@@ -54,7 +54,7 @@ module.exports = {
|
||||
* @param options
|
||||
* @param path
|
||||
*/
|
||||
spawn(command, options = {}, path = boundaryPath) {
|
||||
spawn(command, options = {}, path = resolveCliPath()) {
|
||||
const { onClose, ...spawnOptions } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
const childProcess = spawn(path, command, spawnOptions);
|
||||
|
||||
@@ -14,6 +14,8 @@ const {
|
||||
MenuItem,
|
||||
shell,
|
||||
} = require('electron');
|
||||
// Must be first: copies legacy "Boundary" settings before electron-store loads.
|
||||
require('./helpers/legacy-user-data.js');
|
||||
require('./ipc/handlers.js');
|
||||
const log = require('electron-log/main');
|
||||
|
||||
@@ -68,11 +70,11 @@ log.transports.file.maxSize = 10485760;
|
||||
|
||||
// Immediately log the version information
|
||||
log.info(
|
||||
`Boundary Desktop Client Version: ${config.releaseVersion} | Commit: ${config.releaseCommit}`,
|
||||
`Threshold Desktop Client Version: ${config.releaseVersion} | Commit: ${config.releaseCommit}`,
|
||||
);
|
||||
const cliInfo = version();
|
||||
log.info(
|
||||
`Boundary CLI Version: ${cliInfo.versionNumber} | Commit: ${cliInfo.gitRevision}`,
|
||||
`Threshold CLI Version: ${cliInfo.versionNumber} | Commit: ${cliInfo.gitRevision}`,
|
||||
);
|
||||
|
||||
if (isWindows()) {
|
||||
@@ -158,17 +160,16 @@ const createWindow = async (partition, closeWindowCB) => {
|
||||
});
|
||||
|
||||
// Opens external links in the host default browser.
|
||||
// We allow developer.hashicorp.com domain to open on an external window
|
||||
// and releases.hashicorp.com domain to download the desktop app or
|
||||
// link to the release page for the desktop app or
|
||||
// We allow the upstream Boundary documentation (developer.hashicorp.com,
|
||||
// user-initiated link clicks only) and this fork's project pages to open on
|
||||
// an external window, or
|
||||
// apps.apple.com domain to open the app store page to download the Windows App or
|
||||
// learn.microsoft.com domain to open the documentation for installing Remote Desktop Connection.
|
||||
browserWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (
|
||||
isLocalhost(url) ||
|
||||
url.startsWith('https://developer.hashicorp.com/') ||
|
||||
url.startsWith('https://releases.hashicorp.com/boundary-desktop/') ||
|
||||
url.startsWith('https://support.hashicorp.com/hc/en-us') ||
|
||||
url.startsWith('https://repo.anhonesthost.net/CyberCoveLLC/') ||
|
||||
url.startsWith('https://learn.microsoft.com/') ||
|
||||
url.startsWith('https://apps.apple.com/')
|
||||
) {
|
||||
@@ -282,7 +283,7 @@ app.on('ready', async () => {
|
||||
|
||||
/**
|
||||
* Need for Mac OS behavior of closing the window but not the app.
|
||||
* This allows a user to reopen the window when clicking the Boundary icon in the dock,
|
||||
* This allows a user to reopen the window when clicking the Threshold icon in the dock,
|
||||
* only when the app has not been closed.
|
||||
* More info: https://www.electronjs.org/docs/latest/tutorial/quick-start#open-a-window-if-none-are-open-macos
|
||||
*/
|
||||
|
||||
@@ -261,11 +261,11 @@ handle('getLogPath', () => {
|
||||
case 'win32':
|
||||
return `${
|
||||
process.env.USERPROFILE ?? '%USERPROFILE%'
|
||||
}\\AppData\\Roaming\\Boundary\\logs\\desktop-client.log`;
|
||||
}\\AppData\\Roaming\\Threshold\\logs\\desktop-client.log`;
|
||||
case 'darwin':
|
||||
return '~/Library/Logs/Boundary/desktop-client.log';
|
||||
return '~/Library/Logs/Threshold/desktop-client.log';
|
||||
case 'linux':
|
||||
return '~/.config/Boundary/logs/desktop-client.log';
|
||||
return '~/.config/Threshold/logs/desktop-client.log';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"name": "desktop",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Client UI for Boundary.",
|
||||
"description": "Client UI for Threshold.",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"directories": {
|
||||
"doc": "doc",
|
||||
"test": "tests"
|
||||
|
||||
@@ -1,72 +1 @@
|
||||
<svg width="247" height="78" viewBox="0 0 247 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="247" height="78">
|
||||
<rect width="247" height="78" fill="#C4C4C4"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.164856 -1.62486C-0.0422516 -1.26614 0.0806732 -0.807445 0.439392 -0.600347L143.553 82.0216C143.911 82.2287 144.37 82.1058 144.577 81.7471C144.784 81.3884 144.661 80.9297 144.303 80.7226L1.18936 -1.8994C0.830643 -2.1065 0.371948 -1.98358 0.164856 -1.62486Z" fill="url(#paint0_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.074 -1.62492C15.8669 -1.26621 15.9898 -0.807512 16.3485 -0.600381L151.489 77.4354C151.848 77.6425 152.307 77.5197 152.514 77.161C152.721 76.8023 152.598 76.3436 152.24 76.1364L17.0986 -1.89937C16.7398 -2.1065 16.2811 -1.98362 16.074 -1.62492Z" fill="url(#paint1_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.9481 -1.62477C31.7411 -1.26602 31.8641 -0.807346 32.2228 -0.600296L159.427 72.814C159.785 73.0211 160.244 72.8981 160.451 72.5393C160.658 72.1806 160.535 71.7219 160.177 71.5149L32.9726 -1.89945C32.6138 -2.1065 32.1552 -1.98352 31.9481 -1.62477Z" fill="url(#paint2_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.8574 -1.62475C47.6504 -1.26599 47.7734 -0.807321 48.1321 -0.600283L167.399 68.2279C167.758 68.4349 168.216 68.3119 168.424 67.9531C168.631 67.5944 168.508 67.1357 168.149 66.9287L48.8819 -1.89947C48.5231 -2.1065 48.0644 -1.98351 47.8574 -1.62475Z" fill="url(#paint3_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M63.7667 -1.62481C63.5596 -1.26607 63.6825 -0.807393 64.0413 -0.60032L175.336 63.6416C175.695 63.8487 176.153 63.7257 176.36 63.367C176.567 63.0083 176.444 62.5496 176.086 62.3425L64.7912 -1.89943C64.4324 -2.1065 63.9737 -1.98355 63.7667 -1.62481Z" fill="url(#paint4_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.6327 0.695792C84.4256 1.05451 84.5484 1.5132 84.9072 1.72032L188.229 61.3761C188.588 61.5832 189.047 61.4603 189.254 61.1016C189.461 60.7429 189.338 60.2842 188.98 60.077L85.6572 0.421292C85.2985 0.214178 84.8398 0.337076 84.6327 0.695792Z" fill="url(#paint5_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M159.152 -1.62479C158.945 -1.26604 159.068 -0.807367 159.427 -0.600307L231.001 40.7107C231.36 40.9177 231.818 40.7948 232.025 40.436C232.232 40.0773 232.11 39.6186 231.751 39.4116L160.177 -1.89944C159.818 -2.1065 159.359 -1.98354 159.152 -1.62479ZM143.278 -1.62493C143.071 -1.26623 143.194 -0.807523 143.552 -0.600387L215.091 40.7106C215.45 40.9177 215.909 40.7949 216.116 40.4362C216.323 40.0775 216.2 39.6188 215.842 39.4116L144.303 -1.89936C143.944 -2.1065 143.485 -1.98363 143.278 -1.62493Z" fill="#F04E54"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M175.061 -1.62493C174.854 -1.26623 174.977 -0.807524 175.336 -0.600387L246.875 40.7106C247.234 40.9177 247.692 40.7949 247.899 40.4362C248.106 40.0775 247.984 39.6188 247.625 39.4116L176.086 -1.89936C175.727 -2.1065 175.269 -1.98363 175.061 -1.62493Z" fill="#F04E54"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M232.025 39.6862C231.818 39.3274 231.36 39.2045 231.001 39.4115L159.427 80.7225C159.068 80.9296 158.945 81.3883 159.152 81.747C159.359 82.1058 159.818 82.2287 160.177 82.0217L231.751 40.7107C232.11 40.5036 232.232 40.0449 232.025 39.6862Z" fill="url(#paint6_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M216.116 39.686C215.909 39.3273 215.45 39.2045 215.091 39.4116L143.553 80.7226C143.194 80.9297 143.071 81.3884 143.278 81.7471C143.485 82.1059 143.944 82.2287 144.303 82.0216L215.842 40.7106C216.2 40.5035 216.323 40.0447 216.116 39.686Z" fill="url(#paint7_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.899 39.686C247.692 39.3273 247.234 39.2045 246.875 39.4116L175.336 80.7226C174.977 80.9297 174.854 81.3884 175.061 81.7471C175.269 82.1059 175.727 82.2287 176.086 82.0216L247.625 40.7106C247.984 40.5035 248.106 40.0447 247.899 39.686Z" fill="url(#paint8_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.899 48.8583C247.692 48.4996 247.233 48.3769 246.875 48.5841L191.245 80.7227C190.886 80.9299 190.764 81.3886 190.971 81.7473C191.178 82.1059 191.637 82.2287 191.995 82.0215L247.625 49.8829C247.984 49.6757 248.106 49.217 247.899 48.8583Z" fill="url(#paint9_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.899 58.0307C247.692 57.6721 247.233 57.5493 246.875 57.7565L207.119 80.7227C206.76 80.9299 206.638 81.3886 206.845 81.7473C207.052 82.1059 207.511 82.2287 207.869 82.0215L247.625 59.0553C247.984 58.8481 248.106 58.3894 247.899 58.0307Z" fill="url(#paint10_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M222.754 -1.62469C222.547 -1.26591 222.67 -0.807252 223.029 -0.600248L246.875 13.1583C247.234 13.3653 247.692 13.2423 247.9 12.8835C248.107 12.5247 247.983 12.0661 247.625 11.8591L223.778 -1.8995C223.42 -2.10651 222.961 -1.98347 222.754 -1.62469ZM190.971 -1.62475C190.764 -1.26599 190.887 -0.807319 191.245 -0.600282L246.875 31.5031C247.234 31.7101 247.692 31.5871 247.899 31.2283C248.107 30.8696 247.984 30.4109 247.625 30.2039L191.995 -1.89947C191.636 -2.1065 191.178 -1.98351 190.971 -1.62475ZM206.88 -1.62485C206.673 -1.26613 206.796 -0.80744 207.155 -0.600344L246.875 22.3306C247.234 22.5377 247.692 22.4148 247.899 22.0561C248.107 21.6973 247.984 21.2386 247.625 21.0315L207.904 -1.89941C207.546 -2.1065 207.087 -1.98358 206.88 -1.62485Z" fill="#F04E54"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.899 67.2027C247.692 66.8442 247.233 66.7216 246.874 66.929L223.028 80.7229C222.669 80.9303 222.547 81.3891 222.754 81.7476C222.962 82.1062 223.421 82.2287 223.779 82.0213L247.625 68.2275C247.984 68.0201 248.106 67.5613 247.899 67.2027Z" fill="url(#paint11_linear)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="144.179" y1="40.0474" x2="0.54483" y2="40.0474" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F04E54" stop-opacity="0"/>
|
||||
<stop offset="0.601279" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="152.129" y1="37.7521" x2="16.4447" y2="37.7521" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F04E54" stop-opacity="0"/>
|
||||
<stop offset="0.597199" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="160.078" y1="35.4568" x2="32.3459" y2="35.4568" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F04E54" stop-opacity="0"/>
|
||||
<stop offset="0.592611" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear" x1="168.028" y1="33.1615" x2="48.2463" y2="33.1615" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F04E54" stop-opacity="0"/>
|
||||
<stop offset="0.587415" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear" x1="175.978" y1="30.8662" x2="64.1465" y2="30.8662" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F04E54" stop-opacity="0"/>
|
||||
<stop offset="0.581479" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear" x1="188.884" y1="30.8916" x2="85.004" y2="30.8916" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F04E54" stop-opacity="0"/>
|
||||
<stop offset="0.574635" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear" x1="195.59" y1="81.6273" x2="195.59" y2="39.7836" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1119" stop-color="#CF4A4F"/>
|
||||
<stop offset="0.6877" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear" x1="179.69" y1="81.6268" x2="179.69" y2="39.7838" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1119" stop-color="#CF4A4F"/>
|
||||
<stop offset="0.6877" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear" x1="211.492" y1="81.6267" x2="211.492" y2="39.7836" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1119" stop-color="#CF4A4F"/>
|
||||
<stop offset="0.6877" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear" x1="219.441" y1="81.6273" x2="219.441" y2="48.9628" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1119" stop-color="#CF4A4F"/>
|
||||
<stop offset="0.6877" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear" x1="227.389" y1="81.6274" x2="227.389" y2="58.1437" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1119" stop-color="#CF4A4F"/>
|
||||
<stop offset="0.6877" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear" x1="235.337" y1="81.6273" x2="235.337" y2="67.3245" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1119" stop-color="#CF4A4F"/>
|
||||
<stop offset="0.6877" stop-color="#F04E54"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg width="1" height="1" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 68 B |
@@ -284,7 +284,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
await click(SIGNOUT_BTN);
|
||||
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).isVisible();
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Boundary?');
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Threshold?');
|
||||
|
||||
await click(MODAL_CANCEL_BTN);
|
||||
|
||||
@@ -313,7 +313,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
await click(SIGNOUT_BTN);
|
||||
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).isVisible();
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Boundary?');
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Threshold?');
|
||||
|
||||
await click(MODAL_CONFIRM_BTN);
|
||||
|
||||
@@ -343,7 +343,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
await click(SIGNOUT_BTN);
|
||||
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).isVisible();
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Boundary?');
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Threshold?');
|
||||
|
||||
await triggerEvent(window, 'onAppQuit');
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
await click(SIGNOUT_BTN);
|
||||
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).isVisible();
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Boundary?');
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).includesText('Sign out of Threshold?');
|
||||
|
||||
await click(MODAL_CONFIRM_BTN);
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
isMac: false,
|
||||
isLinux: false,
|
||||
});
|
||||
this.server.get('https://api.releases.hashicorp.com/*', () => {
|
||||
this.server.get('https://releases-api.threshold.invalid/*', () => {
|
||||
return new Response(200, {}, META_DATA_RESPONSE);
|
||||
});
|
||||
this.server.get('/scopes', () => {
|
||||
@@ -344,7 +344,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
// no "response_type" field
|
||||
return new Response(200, {}, { scopes: [] });
|
||||
});
|
||||
this.server.get('https://api.releases.hashicorp.com/*', () => {
|
||||
this.server.get('https://releases-api.threshold.invalid/*', () => {
|
||||
return new Response(200, {}, META_DATA_RESPONSE);
|
||||
});
|
||||
|
||||
@@ -367,7 +367,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
// no "response_type" field
|
||||
return new Response(200, {}, { scopes: [] });
|
||||
});
|
||||
this.server.get('https://api.releases.hashicorp.com/*', () => {
|
||||
this.server.get('https://releases-api.threshold.invalid/*', () => {
|
||||
return new Response(200, {}, META_DATA_RESPONSE);
|
||||
});
|
||||
|
||||
@@ -390,7 +390,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
// no "response_type" field
|
||||
return new Response(200, {}, { scopes: [] });
|
||||
});
|
||||
this.server.get('https://api.releases.hashicorp.com/*', () => {
|
||||
this.server.get('https://releases-api.threshold.invalid/*', () => {
|
||||
return new Response(500);
|
||||
});
|
||||
|
||||
@@ -400,7 +400,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
assert.dom('[data-test-unsupported-controller-alert]').exists();
|
||||
assert
|
||||
.dom('[data-test-releases-link]')
|
||||
.hasAttribute('href', 'https://releases.hashicorp.com/boundary-desktop/');
|
||||
.hasAttribute('href', 'https://releases.threshold.invalid/');
|
||||
});
|
||||
|
||||
test('pagination is not supported - navigate to cluster url page', async function (assert) {
|
||||
@@ -415,7 +415,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
// no "response_type" field
|
||||
return new Response(200, {}, { scopes: [] });
|
||||
});
|
||||
this.server.get('https://api.releases.hashicorp.com/*', () => {
|
||||
this.server.get('https://releases-api.threshold.invalid/*', () => {
|
||||
return new Response(200, {}, META_DATA_RESPONSE);
|
||||
});
|
||||
|
||||
|
||||