apigeelint
Version:
Node module and tool to lint a bundle for an Apigee API Proxy or sharedflow.
169 lines (145 loc) • 4.93 kB
JavaScript
/*
Copyright © 2019-2021,2026 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const assert = require("node:assert"),
child_process = require("node:child_process"),
path = require("node:path"),
fs = require("node:fs"),
tmp = require("tmp"),
debug = require("debug")("apigeelint:cli-test-helper");
/**
* Runs a CLI integration test by setting up a temporary environment,
* performing npm install, mirroring the current source, and spawning the CLI.
*
* @param {Object} options
* @param {string} options.testDir - Directory containing the fixture's package.json
* @param {string[]} options.cliArgs - Arguments to pass to apigeelint CLI
* @param {Function} [options.preExecHook] - Callback to manipulate tmpdir before CLI execution
* @param {Object} [options.env] - Additional environment variables
* @param {Function} checkCb - Callback called with (exitCode, parsedStdout, stderr)
*/
function runCliIntegrationTest(options, checkCb) {
const { testDir, cliArgs, preExecHook, env } = options;
const tmpdir = tmp.dirSync({
prefix: `apigeelint-cli-test`,
keep: false,
unsafeCleanup: true,
});
// make sure to cleanup when the process exits
const cleanup = () => {
try {
tmpdir.removeCallback();
} catch (e) {
// ignore
}
};
process.on("exit", cleanup);
const spawnOpts = {
cwd: tmpdir.name,
encoding: "utf8",
env: { ...process.env, ...env },
};
// 1. Copy fixture's package.json
fs.cpSync(
path.resolve(testDir, "package.json"),
path.resolve(tmpdir.name, "package.json"),
{
force: true,
},
);
if (debug.enabled) {
const r = child_process.spawnSync("which", ["node"], spawnOpts);
debug(`node: ` + JSON.stringify(r));
}
// 2. npm install (can take a long time)
child_process.exec("npm install", spawnOpts, (e, stdout, stderr) => {
if (e) {
cleanup();
return assert.fail(`npm install failed: ${e.message}`);
}
debug("npm install completed");
// 3. Mirror current project source into node_modules/apigeelint
const apigeeLintInTmp = path.resolve(
tmpdir.name,
"node_modules/apigeelint",
);
// Ensure the directory exists
fs.mkdirSync(apigeeLintInTmp, { recursive: true });
// Copy lib, cli.js, and package.json to the tmp node_modules
const rootDir = path.resolve(__dirname, "../../");
fs.cpSync(
path.resolve(rootDir, "lib"),
path.resolve(apigeeLintInTmp, "lib"),
{ recursive: true, force: true },
);
fs.cpSync(
path.resolve(rootDir, "cli.js"),
path.resolve(apigeeLintInTmp, "cli.js"),
{ force: true },
);
// fs.cpSync(
// path.resolve(rootDir, "package.json"),
// path.resolve(apigeeLintInTmp, "package.json"),
// { force: true },
// );
// 4. Run preExecHook if provided
if (preExecHook) {
preExecHook(tmpdir.name);
}
// 5. Spawn the CLI
try {
const proc = child_process.spawn(
"node",
[path.resolve(apigeeLintInTmp, "cli.js"), ...cliArgs],
{ ...spawnOpts, timeout: 60000 },
);
let stdoutBlobs = [],
stderrBlobs = [];
proc.stdout.on("data", (data) => {
stdoutBlobs.push(data);
});
proc.stderr.on("data", (data) => {
stderrBlobs.push(data);
});
proc.on("error", (error) => {
cleanup();
assert.fail(`Process error: ${error}`);
});
proc.on("close", (code) => {
const aggregatedStdout = stdoutBlobs.join("");
const aggregatedStderr = stderrBlobs.join("");
debug(`CLI exited with code ${code}`);
let items = [];
if (aggregatedStdout.trim()) {
debug("stdout:" + aggregatedStdout);
try {
items = JSON.parse(aggregatedStdout);
} catch (eparse) {
// If it's not JSON, we pass the raw string if needed,
// but usually apigeelint output is JSON when format is specified.
debug(`Stdout is not JSON: ${aggregatedStdout}`);
}
}
debug("stderr:" + aggregatedStderr);
checkCb(code, items, aggregatedStderr, tmpdir.name);
cleanup();
process.removeListener("exit", cleanup);
});
} catch (ex) {
cleanup();
assert.fail(`Spawn failed: ${ex.message}`);
}
});
}
module.exports = {
runCliIntegrationTest,
};