qwen-code/scripts/release-script-utils.js
yiliang114 3570f93af9 fix(installer): address release asset review followups
- sh: reject CR/LF in archive entry names before the literal `..` glob so
  a `..\r` entry cannot bypass path validation.
- bat: prefer Tls12+Tls13 in PowerShell helpers, fall back to Tls12 alone
  on older .NET Framework where the Tls13 enum is missing.
- bat: document the implicit `:ValidateOptions` dependency next to the
  qwen.cmd wrapper writer so loosening the validator stays a conscious
  choice.
- build-standalone-release: surface the `xz-utils` host requirement for
  Linux Node downloads in `--help`.
- release-script-utils: support `--key=value` form in `parseCliArgs`.
- tests: cover the new CRLF message, TLS string, and `--key=value` parsing;
  register process-level signal/exit handlers in `ensureMinimalDist` so a
  crashed test still restores `dist/`.
2026-05-06 21:19:19 +08:00

107 lines
2.3 KiB
JavaScript

/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { pipeline } from 'node:stream/promises';
import { fileURLToPath } from 'node:url';
function isMainModule(importMetaUrl) {
const filename = fileURLToPath(importMetaUrl);
return process.argv[1] && path.resolve(process.argv[1]) === filename;
}
function readOptionValue(argv, index, optionName) {
const value = argv[index + 1];
if (!value || value.startsWith('-')) {
fail(`${optionName} requires a value`);
}
return value;
}
function parseCliArgs(argv, options, defaults = {}) {
const args = { ...defaults };
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
let key = arg;
let inlineValue;
if (arg.startsWith('--')) {
const equalsIndex = arg.indexOf('=');
if (equalsIndex > -1) {
key = arg.slice(0, equalsIndex);
inlineValue = arg.slice(equalsIndex + 1);
}
}
const option = options[key];
if (!option) {
fail(`Unknown option: ${arg}`);
}
if (option.type === 'boolean') {
if (inlineValue !== undefined) {
fail(`${key} does not accept a value`);
}
args[option.name] = true;
continue;
}
let value;
if (inlineValue !== undefined) {
if (inlineValue === '') {
fail(`${key} requires a value`);
}
value = inlineValue;
} else {
value = readOptionValue(argv, index, key);
index += 1;
}
if (option.validate) {
option.validate(value);
}
args[option.name] = value;
}
return args;
}
function parseSha256Sums(content) {
const checksums = new Map();
for (const line of content.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const match = /^([0-9a-fA-F]{64})\s+\*?(.+)$/.exec(trimmed);
if (match) {
checksums.set(match[2], match[1].toLowerCase());
}
}
return checksums;
}
async function sha256File(filePath) {
const hash = crypto.createHash('sha256');
await pipeline(fs.createReadStream(filePath), hash);
return hash.digest('hex');
}
function fail(message) {
throw new Error(`ERROR: ${message}`);
}
export {
fail,
isMainModule,
parseCliArgs,
parseSha256Sums,
readOptionValue,
sha256File,
};