blob: 36da44d919e41d009b2eb7ab00c661edbf13537e [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { chmod, copyFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
async function fixture(t, nativeVersion = '1.2.3') {
const root = await mkdtemp(join(tmpdir(), 'maka launcher '));
t.after(() => rm(root, { recursive: true, force: true }));
const target = `${process.platform}-${process.arch}${process.platform === 'linux' ? '-gnu' : ''}`;
const name = '@maka-agent/cli-' + target;
const native = join(root, 'node_modules', name);
await mkdir(join(root, 'bin'), { recursive: true });
await mkdir(join(native, 'bin'), { recursive: true });
const launcher = join(root, 'bin/maka.mjs');
await copyFile(new URL('../bin/maka.mjs', import.meta.url), launcher);
await writeFile(
join(root, 'package.json'),
JSON.stringify({
name: '@maka-agent/cli',
version: '1.2.3',
optionalDependencies: { [name]: '1.2.3' },
}),
);
const executable = process.platform === 'win32' ? 'maka.exe' : 'maka';
await writeFile(
join(native, 'package.json'),
JSON.stringify({
name,
version: nativeVersion,
bin: { maka: 'bin/' + executable },
}),
);
return { root, native, launcher, executable };
}
test('launcher preserves arguments, input, output and native exit status', {
skip: process.platform === 'win32',
}, async (t) => {
const f = await fixture(t);
const binary = join(f.native, 'bin', f.executable);
await writeFile(
binary,
`#!/usr/bin/env node
const fs = require('node:fs');
console.log(JSON.stringify({args:process.argv.slice(2), input:fs.readFileSync(0,'utf8')}));
console.error('native diagnostic');
process.exitCode = 23;
`,
);
await chmod(binary, 0o755);
const args = ['code', 'space and 中文', '; echo never-executed', '--flag=value'];
const result = spawnSync(process.execPath, [f.launcher, ...args], {
input: 'input payload',
encoding: 'utf8',
timeout: 10_000,
});
assert.equal(result.status, 23, result.stderr);
assert.deepEqual(JSON.parse(result.stdout), { args, input: 'input payload' });
assert.equal(result.stderr.trim(), 'native diagnostic');
});
test('launcher refuses a mixed native package version and reports missing optional installs', async (t) => {
const f = await fixture(t, '1.2.2');
const run = () =>
spawnSync(process.execPath, [f.launcher], { encoding: 'utf8', timeout: 10_000 });
const mixed = run();
assert.equal(mixed.status, 1);
assert.match(mixed.stderr, /does not match this launcher/);
await rm(f.native, { recursive: true });
const missing = run();
assert.equal(missing.status, 1);
assert.match(missing.stderr, /--include=optional @maka-agent\/cli@1\.2\.3/);
});