[dev-tool] Create a symbolic link to recordings when running the proxy tool (#27144)

### Packages impacted by this PR

- `@azure-tools/dev-tool`

### Description

This is a quality-of-life change which makes inspecting recordings
easier. With the new asset sync flow, recordings are no longer stored in
the repo with each package. If you want to look at your recordings for
some reason (e.g. after re-recording to make sure you aren't leaking any
secrets), it takes a few extra steps to find them: navigate to the repo
root, look at the `.breadcrumb` file in the `.assets` folder, and then
use that to find the location of the recordings corresponding to your
package.

This PR improves that process by creating a symbolic link to the
recordings from each package, restoring the original flow.
This commit is contained in:
Timo van Veenendaal 2023-09-21 09:39:06 -07:00 коммит произвёл GitHub
Родитель 20321c7ff0
Коммит 418a979e2f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 60 добавлений и 5 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -174,3 +174,6 @@ code-model-*
*.cpuprofile
# Temp typespec files
TempTypeSpecFiles/
# Symbolic link from project directory to recordings
_recordings

Просмотреть файл

@ -1,14 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { ChildProcess, spawn, SpawnOptions } from "child_process";
import { ChildProcess, exec, spawn, SpawnOptions } from "child_process";
import { createPrinter } from "./printer";
import { ProjectInfo, resolveRoot } from "./resolveProject";
import { ProjectInfo, resolveProject, resolveRoot } from "./resolveProject";
import fs from "fs-extra";
import path from "path";
import axios from "axios";
import decompress from "decompress";
import envPaths from "env-paths";
import { promisify } from "util";
const log = createPrinter("test-proxy");
const downloadLocation = path.join(envPaths("azsdk-dev-tool").cache, "test-proxy");
@ -105,10 +106,16 @@ async function downloadTestProxy(downloadLocation: string, downloadUrl: string):
await decompress(Buffer.from(data), downloadLocation);
}
let cachedTestProxyExecutableLocation: string | undefined;
/**
* Gets the path to the test-proxy executable. If the test-proxy executable has not been downloaded already, it will first be downloaded.
*/
export async function getTestProxyExecutable(): Promise<string> {
if (cachedTestProxyExecutableLocation) {
return cachedTestProxyExecutableLocation;
}
const targetVersion = await getTargetVersion();
const binary = await getTestProxyBinary();
@ -132,6 +139,7 @@ export async function getTestProxyExecutable(): Promise<string> {
await fs.chmod(executableLocation, 0o755);
}
cachedTestProxyExecutableLocation = executableLocation;
return executableLocation;
}
@ -164,13 +172,58 @@ function runCommand(executable: string, argv: string[], options: SpawnOptions =
}
export async function runTestProxyCommand(argv: string[]): Promise<void> {
return runCommand(await getTestProxyExecutable(), argv, { stdio: "inherit" }).result;
const result = runCommand(await getTestProxyExecutable(), argv, { stdio: "inherit" }).result;
if (await fs.pathExists("assets.json")) {
await linkRecordingsDirectory();
}
return result;
}
export function createAssetsJson(project: ProjectInfo): Promise<void> {
return runMigrationScript(project, false);
}
const execPromise = promisify(exec);
async function getRecordingsDirectory(project: ProjectInfo): Promise<string> {
const { stdout } = await execPromise(`${await getTestProxyExecutable()} config locate -a assets.json`, { cwd: project.path });
const lines = stdout.split("\n");
// the directory is the second-to-last line of output (there's some other log output that comes out from the test proxy first, and the last line is empty)
return lines[lines.length - 2].trim();
}
export async function linkRecordingsDirectory() {
const project = await resolveProject();
const root = await resolveRoot();
const recordingsDirectory = await getRecordingsDirectory(project);
const projectRelativeToRoot = path.relative(root, project.path);
const trueRecordingsDirectory = path.join(recordingsDirectory, projectRelativeToRoot, 'recordings/');
const relativeRecordingsDirectory = path.relative(project.path, trueRecordingsDirectory);
const symlinkLocation = path.join(project.path, "_recordings");
if (await fs.pathExists(symlinkLocation)) {
const stat = await fs.lstat(symlinkLocation);
if (stat.isSymbolicLink()) {
await fs.unlink(symlinkLocation);
} else {
log.warn("Could not create symbolic link to recordings directory: a file exists at _recordings already.");
return;
}
}
// Try and create a symlink but fail gracefully if it doesn't work
try {
await fs.symlink(relativeRecordingsDirectory, symlinkLocation);
} catch (e) {
log.warn("Could not create symbolic link to recordings directory");
log.warn(e);
}
}
export async function runMigrationScript(
project: ProjectInfo,
initialPush: boolean
@ -219,8 +272,7 @@ export async function isProxyToolActive(): Promise<boolean> {
await axios.get(`http://localhost:${process.env.TEST_PROXY_HTTP_PORT ?? 5000}/info/available`);
log.info(
`Proxy tool seems to be active at http://localhost:${
process.env.TEST_PROXY_HTTP_PORT ?? 5000
`Proxy tool seems to be active at http://localhost:${process.env.TEST_PROXY_HTTP_PORT ?? 5000
}\n`
);
return true;