feat(deploy): support positional release inputs

This commit is contained in:
DeCentN2Madness committed 2026-11-07 14:02:35 -07:00
1 parent 2fca442922
commit a643383e79
3 files changed
+150 -25

No files matched your search

+46 -1
View File
@@ -5,7 +5,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import test from 'node:test';
import { releaseManifest, rsyncArguments, sshTarget } from './deploy.mjs';
import { deploymentInputs, parseArgs, portalInput, releaseManifest, rsyncArguments, rsyncTransport, sshTarget } from './deploy.mjs';
const fixturePath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'fixtures', 'contabo-release-manifest-v1.json');
const fixture = JSON.parse(await fs.readFile(fixturePath, 'utf8'));
@@ -42,3 +42,48 @@ test('deployment transfer uses the Contabo ownership and mode interface', () =>
assert.deepEqual(sshTarget('root@host.example', '/absolute/key').slice(0, 3), ['ssh','-i','/absolute/key']);
assert.throws(() => sshTarget('host;rm -rf /', '/absolute/key'), /invalid/);
});
test('deployment accepts a positional OpenSSH alias and ciphertext ahead of environment settings', () => {
const options = parseArgs(['--dry-run', 'aiar', '/private files/portal ciphertext.json']);
const inputs = deploymentInputs(options, {
AIA_DEPLOY_HOST: 'wrong.example',
AIA_DEPLOY_SSH_IDENTITY: '/wrong/key',
AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE: '/wrong/payload.json',
});
assert.deepEqual(inputs, {
host: 'aiar', identity: undefined, ciphertext: '/private files/portal ciphertext.json', allowSshConfig: true, source: 'positional',
});
assert.deepEqual(sshTarget(inputs.host, inputs.identity, { allowSshConfig: inputs.allowSshConfig }), ['ssh']);
assert.equal(rsyncTransport(['ssh']), "'ssh'");
});
test('deployment accepts positional explicit identities without shell-splitting paths', () => {
const options = parseArgs(['--confirm', 'root@66.94.121.180', '/keys/AIA Marketing', '/private files/portal ciphertext.json']);
const inputs = deploymentInputs(options, {});
assert.equal(inputs.allowSshConfig, false);
assert.equal(inputs.identity, '/keys/AIA Marketing');
assert.deepEqual(sshTarget(inputs.host, inputs.identity), ['ssh', '-i', '/keys/AIA Marketing', '-o', 'IdentitiesOnly=yes']);
assert.equal(rsyncTransport(sshTarget(inputs.host, inputs.identity)), "'ssh' '-i' '/keys/AIA Marketing' '-o' 'IdentitiesOnly=yes'");
});
test('deployment retains environment-only compatibility and rejects malformed positional input', () => {
const env = {
AIA_DEPLOY_HOST: 'root@host.example',
AIA_DEPLOY_SSH_IDENTITY: '/absolute/key',
AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE: '/private/payload.json',
};
assert.deepEqual(deploymentInputs(parseArgs(['--confirm']), env), {
host: env.AIA_DEPLOY_HOST, identity: env.AIA_DEPLOY_SSH_IDENTITY, ciphertext: env.AIA_PORTAL_PRODUCTION_CIPHERTEXT_FILE, allowSshConfig: false, source: 'environment',
});
assert.throws(() => parseArgs(['--dry-run', 'aiar']), /zero, two, or three positional/);
assert.throws(() => parseArgs(['--dry-run', '--confirm']), /Choose exactly one/);
assert.throws(() => deploymentInputs(parseArgs(['--dry-run', 'aiar', 'relative.json']), {}), /must be absolute/);
assert.throws(() => deploymentInputs(parseArgs(['--dry-run', 'aiar', 'relative-key', '/private/payload.json']), {}), /must be an absolute path/);
});
test('a supplied ciphertext path must exist and be safe before a deployment build starts', async () => {
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), 'aia-deploy-missing-ciphertext-'));
try {
await assert.rejects(portalInput(path.join(temporary, 'missing.json'), temporary), /Portal ciphertext is unavailable or unsafe/);
} finally { await fs.rm(temporary, { recursive: true, force: true }); }
});