diff --git a/package.json b/package.json index 67f613f..13751cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hemlang/hpm", - "version": "1.0.5", + "version": "1.0.6", "description": "Hemlock Package Manager - A package manager for the Hemlock programming language", "author": "Hemlock Team", "license": "MIT", diff --git a/src/cache.hml b/src/cache.hml index c05f258..4b15c9e 100644 --- a/src/cache.hml +++ b/src/cache.hml @@ -1,6 +1,6 @@ // hpm - Global cache management -import { exists, read_file, write_file, make_dir, list_dir, remove_file, remove_dir, is_dir } from "@stdlib/fs"; +import { exists, read_file, write_file, make_dir, list_dir, remove_file, remove_dir, is_dir, file_stat } from "@stdlib/fs"; import * as env from "@stdlib/env"; import { parse, stringify, pretty } from "@stdlib/json"; import { exec } from "@stdlib/process"; @@ -248,9 +248,10 @@ export fn get_cache_size(): i64 { while (i < packages.length) { let pkg = packages[i]; try { - let content = read_file(pkg.path); - if (content != null) { - total = total + content.length; + // Use file_stat to get file size without reading entire file into memory + let stat = file_stat(pkg.path); + if (stat != null && stat.size >= 0) { + total = total + stat.size; } } catch (e) { // Ignore errors diff --git a/src/github.hml b/src/github.hml index 922effa..b882dbb 100644 --- a/src/github.hml +++ b/src/github.hml @@ -4,12 +4,20 @@ import { get, get_binary, is_success } from "@stdlib/http"; import { parse } from "@stdlib/json"; import * as env from "@stdlib/env"; import { exists, read_file, write_file } from "@stdlib/fs"; -import { sleep } from "@stdlib/time"; +import { sleep, now } from "@stdlib/time"; +import { getpid } from "@stdlib/process"; // Retry configuration let MAX_RETRIES = 4; let INITIAL_BACKOFF_MS = 1000; // 1 second +// Generate a unique suffix for temp files using PID and timestamp +fn unique_suffix(): string { + let pid = getpid(); + let ts = now(); + return "" + pid + "_" + ts; +} + // Get GitHub token from environment or config export fn get_token() { // First check environment variable @@ -48,7 +56,7 @@ fn github_request(url: string) { } headers.push("Accept: application/vnd.github.v3+json"); - headers.push("User-Agent: hpm/1.0.0"); + headers.push("User-Agent: hpm/1.0.6"); let retries = 0; let backoff_ms = INITIAL_BACKOFF_MS; @@ -222,7 +230,7 @@ export fn get_tarball_url(owner: string, repo: string, version: string): string // Returns the path to the downloaded file export fn download_tarball(owner: string, repo: string, version: string): string { let url = get_tarball_url(owner, repo, version); - let tmpfile = "/tmp/hpm_tarball_" + owner + "_" + repo + "_" + version + ".tar.gz"; + let tmpfile = "/tmp/hpm_tarball_" + owner + "_" + repo + "_" + version + "_" + unique_suffix() + ".tar.gz"; // Build headers with auth if available let headers = []; @@ -270,7 +278,7 @@ export fn repo_exists(owner: string, repo: string): bool { } headers.push("Accept: application/vnd.github.v3+json"); - headers.push("User-Agent: hpm/1.0.0"); + headers.push("User-Agent: hpm/1.0.6"); let retries = 0; let backoff_ms = INITIAL_BACKOFF_MS; diff --git a/src/installer.hml b/src/installer.hml index ff696e4..bae00c7 100644 --- a/src/installer.hml +++ b/src/installer.hml @@ -2,8 +2,9 @@ import { exists, read_file, write_file, make_dir, remove_dir, list_dir, is_dir } from "@stdlib/fs"; import { sha256 } from "@stdlib/hash"; -import { fork, waitpid, exec } from "@stdlib/process"; +import { fork, waitpid, exec, getpid } from "@stdlib/process"; import { exit } from "@stdlib/env"; +import { now } from "@stdlib/time"; import * as github from "./github.hml"; import * as cache from "./cache.hml"; @@ -12,6 +13,13 @@ import * as manifest from "./manifest.hml"; // Maximum number of parallel downloads let MAX_PARALLEL_DOWNLOADS = 4; +// Generate a unique suffix for temp directories using PID and timestamp +fn unique_suffix(): string { + let pid = getpid(); + let ts = now(); + return "" + pid + "_" + ts; +} + // Install a resolved package to hem_modules // Set offline=true to install only from cache (no network requests) export fn install_package(pkg_name: string, resolution, project_dir: string, verbose: bool, offline: bool) { @@ -73,8 +81,8 @@ export fn install_package(pkg_name: string, resolution, project_dir: string, ver } make_dir(install_dir); - // Create temp extraction dir - let extract_dir = "/tmp/hpm_extract_" + owner + "_" + repo + "_" + version; + // Create temp extraction dir with unique suffix to avoid race conditions + let extract_dir = "/tmp/hpm_extract_" + owner + "_" + repo + "_" + version + "_" + unique_suffix(); if (exists(extract_dir)) { remove_dir_recursive(extract_dir); } @@ -102,8 +110,17 @@ export fn install_package(pkg_name: string, resolution, project_dir: string, ver // Clean up temp extraction dir remove_dir_recursive(extract_dir); - // Calculate integrity from file - let integrity = "sha256-unknown"; // TODO: calculate sha256 of tarball file + // Calculate integrity from tarball file + let integrity = "sha256-unknown"; + try { + let tarball_content = read_file(tarball_path); + if (tarball_content != null) { + let hash = sha256(tarball_content); + integrity = "sha256-" + hash; + } + } catch (e) { + // Keep unknown integrity if calculation fails + } return integrity; } diff --git a/src/main.hml b/src/main.hml index 2f79900..2cddd97 100644 --- a/src/main.hml +++ b/src/main.hml @@ -15,7 +15,7 @@ import * as installer from "./installer.hml"; import * as cache from "./cache.hml"; // Version info -let HPM_VERSION = "1.0.5"; +let HPM_VERSION = "1.0.6"; // Exit codes let EXIT_SUCCESS = 0; @@ -90,7 +90,7 @@ fn get_positional_args(): array { let i = 2; // Skip script name and command while (i < args.length) { let arg = args[i]; - if (arg[0] != '-') { + if (arg.length > 0 && arg[0] != '-') { result.push(arg); } i = i + 1; @@ -203,7 +203,7 @@ fn cmd_install() { let sorted = semver.sort(versions); let latest = sorted[sorted.length - 1]; // Remove 'v' prefix - if (latest[0] == 'v') { + if (latest.length > 0 && latest[0] == 'v') { latest = latest.substr(1, latest.length - 1); } version_constraint = "^" + latest; @@ -475,7 +475,7 @@ fn cmd_list() { let arg = args[i]; if (arg.starts_with("--depth=")) { let val = arg.substr(8, arg.length - 8); - depth = parse_int(val); + depth = semver.parse_int(val); } i = i + 1; } @@ -563,21 +563,6 @@ fn print_tree(children: array, prefix: string, max_depth: i32, current_depth: i3 } } -fn parse_int(s: string): i32 { - let result = 0; - let i = 0; - while (i < s.length) { - let ch = s[i]; - if (ch >= '0' && ch <= '9') { - let digit: i32 = ch; - digit = digit - 48; - result = result * 10 + digit; - } - i = i + 1; - } - return result; -} - // hpm outdated fn cmd_outdated() { let project_dir = get_project_dir(); @@ -622,14 +607,14 @@ fn cmd_outdated() { if (versions.length > 0) { let sorted = semver.sort(versions); let latest = sorted[sorted.length - 1]; - if (latest[0] == 'v') { + if (latest.length > 0 && latest[0] == 'v') { latest = latest.substr(1, latest.length - 1); } // Find wanted (highest satisfying constraint) let parsed_constraint = semver.parse_constraint(constraint); let wanted = semver.max_satisfying(versions, parsed_constraint); - if (wanted != null && wanted[0] == 'v') { + if (wanted != null && wanted.length > 0 && wanted[0] == 'v') { wanted = wanted.substr(1, wanted.length - 1); } if (wanted == null) { diff --git a/src/semver.hml b/src/semver.hml index 3382034..84f3b66 100644 --- a/src/semver.hml +++ b/src/semver.hml @@ -73,7 +73,7 @@ export fn stringify(ver): string { } // Helper: Parse string to integer -fn parse_int(s: string): i32 { +export fn parse_int(s: string): i32 { let result = 0; let i = 0; let negative = false; @@ -165,13 +165,13 @@ export fn parse_constraint(constraint_str: string) { } // Check for caret range (^) - if (s[0] == '^') { + if (s.length > 0 && s[0] == '^') { let ver = parse(s.substr(1, s.length - 1)); return { type: "caret", version: ver }; } // Check for tilde range (~) - if (s[0] == '~') { + if (s.length > 0 && s[0] == '~') { let ver = parse(s.substr(1, s.length - 1)); return { type: "tilde", version: ver }; } @@ -189,13 +189,13 @@ export fn parse_constraint(constraint_str: string) { } // Check for > - if (s[0] == '>') { + if (s.length > 0 && s[0] == '>') { let ver = parse(s.substr(1, s.length - 1).trim()); return { type: "gt", version: ver }; } // Check for < - if (s[0] == '<') { + if (s.length > 0 && s[0] == '<') { let ver = parse(s.substr(1, s.length - 1).trim()); return { type: "lt", version: ver }; }