Date: Thu, 26 Mar 2026 21:20:48 +0000 From: Yuri Victorovich <yuri@FreeBSD.org> To: ports-committers@FreeBSD.org, dev-commits-ports-all@FreeBSD.org, dev-commits-ports-main@FreeBSD.org Subject: git: 0eba64bd34ce - main - misc/github-copilot-cli: Add script and binary flavors Message-ID: <69c5a330.31ed3.6b679361@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by yuri: URL: https://cgit.FreeBSD.org/ports/commit/?id=0eba64bd34ce2d85b62754c746b6b84248de60fa commit 0eba64bd34ce2d85b62754c746b6b84248de60fa Author: Yuri Victorovich <yuri@FreeBSD.org> AuthorDate: 2026-03-26 19:55:15 +0000 Commit: Yuri Victorovich <yuri@FreeBSD.org> CommitDate: 2026-03-26 21:20:44 +0000 misc/github-copilot-cli: Add script and binary flavors script flavor (default): existing behavior with all npm dependencies binary flavor (-bin): C launcher binary with all JS and Node.js embedded in the binary, requires only node at runtime (no Node.js, no npm packages, no native modules) Node.js SEA (Single Executable Application) cannot be used because postject_find_resource() has no FreeBSD code path. The binary flavor instead uses a C launcher that embeds all copilot JS files as a gzip- compressed tar archive, extracts to ~/.cache/github-copilot-cli/v<ver>/ on first run, and executes them with the system node. --- misc/github-copilot-cli/Makefile | 131 +++++++++++++++++----- misc/github-copilot-cli/files/launcher.c | 187 +++++++++++++++++++++++++++++++ misc/github-copilot-cli/pkg-plist.binary | 1 + 3 files changed, 293 insertions(+), 26 deletions(-) diff --git a/misc/github-copilot-cli/Makefile b/misc/github-copilot-cli/Makefile index 9c1841342d95..944c59e1ca63 100644 --- a/misc/github-copilot-cli/Makefile +++ b/misc/github-copilot-cli/Makefile @@ -2,8 +2,7 @@ PORTNAME= github-copilot-cli DISTVERSION= 1.0.10 PORTEPOCH= 1 CATEGORIES= misc # machine-learning -DISTFILES= ${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX} \ - ${NODE_HEADERS}${EXTRACT_SUFX} +DISTFILES= ${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX} DIST_SUBDIR= ${PORTNAME} MAINTAINER= yuri@FreeBSD.org @@ -13,9 +12,28 @@ WWW= https://github.com/github/copilot-cli ONLY_FOR_ARCHS= aarch64 amd64 ONLY_FOR_ARCHS_REASON= binaries are installed in folders with architecture encoded in them, patches are welcome to fix this limitation +FLAVORS= script binary +FLAVOR?= ${FLAVORS:[1]} +script_PKGNAMESUFFIX= +binary_PKGNAMESUFFIX= -bin +binary_COMMENT= GitHub Copilot CLI - standalone binary (no npm dependencies) +binary_PLIST= ${.CURDIR}/pkg-plist.binary + FETCH_DEPENDS= npm:www/npm \ jq:textproc/jq \ ${LOCALBASE}/share/certs/ca-root-nss.crt:security/ca_root_nss + +WRKSRC= ${WRKDIR}/copilot-${DISTVERSION} + +PACKAGE_NAME= @github/copilot + +DD= ${DISTDIR}/${DIST_SUBDIR} + +FETCH_SCRIPT= ${PORTSDIR}/Tools/scripts/npmjs-fetch-with-dependencies.sh + +.if ${FLAVOR} == script +DISTFILES+= ${NODE_HEADERS}${EXTRACT_SUFX} + BUILD_DEPENDS= npm:www/npm \ libsecret>0:security/libsecret \ vips>=8.17.2:graphics/vips @@ -25,18 +43,43 @@ RUN_DEPENDS= libsecret>0:security/libsecret \ USES= nodejs:run pkgconfig python:build -WRKSRC= ${WRKDIR}/copilot-${DISTVERSION} +.elif ${FLAVOR} == binary +DISTFILES+= ${NODE_HEADERS}${EXTRACT_SUFX} -PACKAGE_NAME= @github/copilot +BUILD_DEPENDS= npm:www/npm \ + libsecret>0:security/libsecret \ + vips>=8.17.2:graphics/vips -NODE_HEADERS= node-v22.19.0-headers +# The node binary is bundled inside the port binary; its shared libraries +# must still be present at runtime so they are listed here. +LIB_DEPENDS= libada.so:devel/libada \ + libbrotlidec.so:archivers/brotli \ + libcares.so:dns/c-ares \ + libgtest.so:devel/googletest \ + libhdr_histogram.so:graphics/hdr_histogram \ + libicui18n.so:devel/icu \ + libllhttp.so:www/llhttp \ + libmerve.so:devel/merve \ + libnbytes.so:www/nbytes \ + libnghttp2.so:www/libnghttp2 \ + libnghttp3.so:www/libnghttp3 \ + libngtcp2.so:net/libngtcp2 \ + libsimdjson.so:devel/simdjson \ + libsimdutf.so:converters/simdutf \ + libsqlite3.so:databases/sqlite3 \ + libuv.so:devel/libuv \ + libuvwasi.so:devel/uvwasi \ + libzstd.so:archivers/zstd +RUN_DEPENDS= libsecret>0:security/libsecret \ + vips>=8.17.2:graphics/vips -JS_ARCH= ${ARCH:S/amd64/x64/:S/aarch64/arm64/} -PLIST_SUB= JS_ARCH=${JS_ARCH} +USES= nodejs:build pkgconfig python:build -DD= ${DISTDIR}/${DIST_SUBDIR} +.endif # FLAVOR -FETCH_SCRIPT= ${PORTSDIR}/Tools/scripts/npmjs-fetch-with-dependencies.sh +NODE_HEADERS= node-v22.19.0-headers + +JS_ARCH= ${ARCH:S/amd64/x64/:S/aarch64/arm64/} DEP_MODULES= pty sharp keytar node_addon_api dep_pty_npm_name= @devm33/node-pty @@ -52,41 +95,49 @@ dep_node_addon_api_version= 8.5.0 DISTFILES+= ${dep:S/_/-/g}-${dep_${dep}_version}${EXTRACT_SUFX} .endfor +PLIST_SUB= JS_ARCH=${JS_ARCH} + do-fetch: - @if ! [ -f ${DD}/${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX} ] || \ - ! [ -f ${DD}/${NODE_HEADERS}${EXTRACT_SUFX} ] || \ - ! [ -f ${DD}/pty-${dep_pty_version}${EXTRACT_SUFX} ] || \ - ! [ -f ${DD}/sharp-${dep_sharp_version}${EXTRACT_SUFX} ] || \ - ! [ -f ${DD}/keytar-${dep_keytar_version}${EXTRACT_SUFX} ] || \ - ! [ -f ${DD}/node-addon-api-${dep_node_addon_api_version}${EXTRACT_SUFX} ]; then \ - ${MKDIR} ${DD} && \ + @${MKDIR} ${DD} + @if ! [ -f ${DD}/${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX} ]; then \ + ${ECHO} "====> Fetching ${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX}" && \ + ${SETENV} TMPDIR=${WRKDIR} LOCALBASE=${LOCALBASE} ${FETCH_SCRIPT} \ + ${PACKAGE_NAME} ${DISTVERSION} \ + ${FILESDIR}/package-lock.json \ + ${DD}/${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX}; \ + fi + @if ! [ -f ${DD}/${NODE_HEADERS}${EXTRACT_SUFX} ]; then \ ${ECHO} "====> Fetching ${NODE_HEADERS}${EXTRACT_SUFX}" && \ - ${FETCH_CMD} -q https://nodejs.org/download/release/v22.19.0/${NODE_HEADERS}${EXTRACT_SUFX} -o ${DD}/${NODE_HEADERS}${EXTRACT_SUFX} && \ + ${FETCH_CMD} -q https://nodejs.org/download/release/v22.19.0/${NODE_HEADERS}${EXTRACT_SUFX} \ + -o ${DD}/${NODE_HEADERS}${EXTRACT_SUFX}; \ + fi + @if ! [ -f ${DD}/pty-${dep_pty_version}${EXTRACT_SUFX} ]; then \ ${ECHO} "====> Fetching dependency pty" && \ ${SETENV} TMPDIR=${WRKDIR} LOCALBASE=${LOCALBASE} ${FETCH_SCRIPT} \ ${dep_pty_npm_name} ${dep_pty_version} \ ${FILESDIR}/package-lock-pty.json \ - ${DD}/pty-${dep_pty_version}${EXTRACT_SUFX} && \ + ${DD}/pty-${dep_pty_version}${EXTRACT_SUFX}; \ + fi + @if ! [ -f ${DD}/sharp-${dep_sharp_version}${EXTRACT_SUFX} ]; then \ ${ECHO} "====> Fetching dependency sharp" && \ ${SETENV} TMPDIR=${WRKDIR} LOCALBASE=${LOCALBASE} ${FETCH_SCRIPT} \ ${dep_sharp_npm_name} ${dep_sharp_version} \ ${FILESDIR}/package-lock-sharp.json \ - ${DD}/sharp-${dep_sharp_version}${EXTRACT_SUFX} && \ + ${DD}/sharp-${dep_sharp_version}${EXTRACT_SUFX}; \ + fi + @if ! [ -f ${DD}/keytar-${dep_keytar_version}${EXTRACT_SUFX} ]; then \ ${ECHO} "====> Fetching dependency keytar" && \ ${SETENV} TMPDIR=${WRKDIR} LOCALBASE=${LOCALBASE} ${FETCH_SCRIPT} \ ${dep_keytar_npm_name} ${dep_keytar_version} \ ${FILESDIR}/package-lock-keytar.json \ - ${DD}/keytar-${dep_keytar_version}${EXTRACT_SUFX} && \ + ${DD}/keytar-${dep_keytar_version}${EXTRACT_SUFX}; \ + fi + @if ! [ -f ${DD}/node-addon-api-${dep_node_addon_api_version}${EXTRACT_SUFX} ]; then \ ${ECHO} "====> Fetching dependency node-addon-api" && \ ${SETENV} TMPDIR=${WRKDIR} LOCALBASE=${LOCALBASE} ${FETCH_SCRIPT} \ ${dep_node_addon_api_npm_name} ${dep_node_addon_api_version} \ ${FILESDIR}/package-lock-node-addon-api.json \ - ${DD}/node-addon-api-${dep_node_addon_api_version}${EXTRACT_SUFX} && \ - ${ECHO} "====> Fetching ${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX}" && \ - ${SETENV} TMPDIR=${WRKDIR} LOCALBASE=${LOCALBASE} ${FETCH_SCRIPT} \ - ${PACKAGE_NAME} ${DISTVERSION} \ - ${FILESDIR}/package-lock.json \ - ${DD}/${PORTNAME}-${DISTVERSION}${EXTRACT_SUFX}; \ + ${DD}/node-addon-api-${dep_node_addon_api_version}${EXTRACT_SUFX}; \ fi post-extract: @@ -121,8 +172,33 @@ do-build: ${SETENV} HOME=${WRKDIR} CFLAGS="-I${LOCALBASE}/include" CXXFLAGS="-I${LOCALBASE}/include" \ npm rebuild --nodedir=${LOCALBASE} && \ ${CP} build/Release/keytar.node ${WRKSRC}/node_modules/${PACKAGE_NAME}/prebuilds/freebsd-x64/ +.if ${FLAVOR} == binary + @${ECHO_MSG} "====> Creating copilot bundle (includes node runtime)..." + # Copy @img/sharp-freebsd-x64 into copilot's node_modules so it is findable + # after extraction to the cache dir (node resolves it relative to app.js) + @${MKDIR} ${WRKSRC}/node_modules/${PACKAGE_NAME}/node_modules/@img + @${CP} -r ${WRKSRC}/node_modules/@img/sharp-freebsd-x64 \ + ${WRKSRC}/node_modules/${PACKAGE_NAME}/node_modules/@img/ + # Embed the node runtime so it runs without a system node installation + @${CP} ${LOCALBASE}/bin/node ${WRKSRC}/node_modules/${PACKAGE_NAME}/node + @cd ${WRKSRC}/node_modules/${PACKAGE_NAME} && \ + ${TAR} --exclude=./ripgrep --exclude=./sharp \ + --exclude=./changelog.json --exclude=./npm-loader.js.orig \ + -cJf ${WRKSRC}/copilot_bundle.txz . + @${ECHO_MSG} "====> Building copilot launcher..." + @${PRINTF} '\t.global _binary_copilot_bundle_txz_start\n\t.global _binary_copilot_bundle_txz_end\n_binary_copilot_bundle_txz_start:\n\t.incbin "%s"\n_binary_copilot_bundle_txz_end:\n' \ + "${WRKSRC}/copilot_bundle.txz" > ${WRKSRC}/blob.s + @${CC} -c ${WRKSRC}/blob.s -o ${WRKSRC}/blob.o + @${CC} ${CFLAGS} \ + -DPREFIX='"${PREFIX}"' \ + -DPORTVERSION='"${PORTVERSION}"' \ + -c ${FILESDIR}/launcher.c -o ${WRKSRC}/launcher.o + @${CC} ${LDFLAGS} -o ${WRKSRC}/copilot ${WRKSRC}/launcher.o ${WRKSRC}/blob.o + @${STRIP_CMD} ${WRKSRC}/copilot +.endif # FLAVOR == binary do-install: +.if ${FLAVOR} == script # install files cd ${WRKSRC} && \ ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/lib @@ -148,5 +224,8 @@ do-install: @${RLN} -s ${STAGEDIR}${PREFIX}/lib/node_modules/.bin/copilot ${STAGEDIR}${PREFIX}/bin/copilot # strip binaries @${FIND} ${STAGEDIR}${PREFIX}/lib/node_modules/${PACKAGE_NAME} -path "*/build/*" -name *.node | ${XARGS} ${STRIP_CMD} +.elif ${FLAVOR} == binary + ${INSTALL_PROGRAM} ${WRKSRC}/copilot ${STAGEDIR}${PREFIX}/bin/copilot +.endif # FLAVOR .include <bsd.port.mk> diff --git a/misc/github-copilot-cli/files/launcher.c b/misc/github-copilot-cli/files/launcher.c new file mode 100644 index 000000000000..ee3104f1a5cd --- /dev/null +++ b/misc/github-copilot-cli/files/launcher.c @@ -0,0 +1,187 @@ +/* + * launcher.c - GitHub Copilot CLI binary flavor launcher + * + * Extracts the bundled copilot JS files and node runtime to a version-specific + * cache directory and runs them. The bundle (xz-compressed tar) contains the + * full node binary so no system node installation is required. + * + * Node.js SEA (Single Executable Application) cannot be used because + * postject_find_resource() has no FreeBSD code path. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifndef PORTVERSION +#define PORTVERSION "unknown" +#endif + +#define CACHE_SUBDIR "github-copilot-cli/v" PORTVERSION + +extern const char _binary_copilot_bundle_txz_start[]; +extern const char _binary_copilot_bundle_txz_end[]; + +static int +makedirs(const char *path) +{ + char buf[1024]; + char *p; + struct stat st; + + if (stat(path, &st) == 0) + return (0); + + snprintf(buf, sizeof(buf), "%s", path); + for (p = buf + 1; *p != '\0'; p++) { + if (*p == '/') { + *p = '\0'; + if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0 && + errno != EEXIST) + return (-1); + *p = '/'; + } + } + return (mkdir(buf, 0755) == 0 || errno == EEXIST ? 0 : -1); +} + +static int +extract_bundle(const char *destdir) +{ + char tmpdir[1024], archpath[1024]; + const char *data; + size_t size; + FILE *f; + pid_t pid; + int status; + + /* Write bundle to a temp archive file */ + snprintf(archpath, sizeof(archpath), "%s/.bundle.txz.tmp", destdir); + data = _binary_copilot_bundle_txz_start; + size = (size_t)(_binary_copilot_bundle_txz_end - + _binary_copilot_bundle_txz_start); + + f = fopen(archpath, "wb"); + if (f == NULL) { + fprintf(stderr, "copilot: cannot write bundle: %s\n", + strerror(errno)); + return (-1); + } + if (fwrite(data, 1, size, f) != size) { + fclose(f); + unlink(archpath); + fprintf(stderr, "copilot: bundle write failed\n"); + return (-1); + } + fclose(f); + + /* Extract into a temp subdir, then atomically rename */ + snprintf(tmpdir, sizeof(tmpdir), "%s.extracting", destdir); + makedirs(tmpdir); + + pid = fork(); + if (pid < 0) { + unlink(archpath); + return (-1); + } + if (pid == 0) { + execl("/usr/bin/tar", "tar", "-xJf", archpath, "-C", tmpdir, + (char *)NULL); + _exit(127); + } + waitpid(pid, &status, 0); + unlink(archpath); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + fprintf(stderr, "copilot: bundle extraction failed\n"); + /* cleanup tmpdir */ + pid = fork(); + if (pid == 0) { + execl("/bin/rm", "rm", "-rf", tmpdir, (char *)NULL); + _exit(1); + } + if (pid > 0) + waitpid(pid, NULL, 0); + return (-1); + } + + /* Atomic rename: tmpdir -> destdir */ + if (rename(tmpdir, destdir) != 0) { + /* Another process may have already created destdir - that's ok */ + if (errno != EEXIST && errno != ENOTEMPTY) { + fprintf(stderr, "copilot: rename failed: %s\n", + strerror(errno)); + return (-1); + } + /* Use existing destdir; clean up tmpdir */ + pid = fork(); + if (pid == 0) { + execl("/bin/rm", "rm", "-rf", tmpdir, (char *)NULL); + _exit(1); + } + if (pid > 0) + waitpid(pid, NULL, 0); + } + + return (0); +} + +int +main(int argc, char *argv[]) +{ + const char *xdg, *home; + char cache_dir[1024], node_path[1080], script_path[1080]; + char **new_argv; + struct stat st; + int i; + + /* Determine cache directory */ + xdg = getenv("XDG_CACHE_HOME"); + home = getenv("HOME"); + if (xdg != NULL && xdg[0] != '\0') + snprintf(cache_dir, sizeof(cache_dir), "%s/" CACHE_SUBDIR, xdg); + else if (home != NULL && home[0] != '\0') + snprintf(cache_dir, sizeof(cache_dir), + "%s/.cache/" CACHE_SUBDIR, home); + else { + fprintf(stderr, + "copilot: HOME or XDG_CACHE_HOME must be set\n"); + return (1); + } + + snprintf(node_path, sizeof(node_path), "%s/node", cache_dir); + snprintf(script_path, sizeof(script_path), "%s/index.js", cache_dir); + + /* Extract bundle if not already done (check for node binary) */ + if (stat(node_path, &st) != 0) { + if (makedirs(cache_dir) != 0) { + fprintf(stderr, "copilot: cannot create %s: %s\n", + cache_dir, strerror(errno)); + return (1); + } + if (extract_bundle(cache_dir) != 0) + return (1); + } + + /* Build argument vector for the bundled node */ + new_argv = malloc((size_t)(argc + 2) * sizeof(char *)); + if (new_argv == NULL) { + fprintf(stderr, "copilot: malloc failed\n"); + return (1); + } + new_argv[0] = node_path; + new_argv[1] = script_path; + for (i = 1; i < argc; i++) + new_argv[i + 1] = argv[i]; + new_argv[argc + 1] = NULL; + + execv(node_path, new_argv); + + fprintf(stderr, "copilot: cannot exec %s: %s\n", node_path, + strerror(errno)); + return (1); +} diff --git a/misc/github-copilot-cli/pkg-plist.binary b/misc/github-copilot-cli/pkg-plist.binary new file mode 100644 index 000000000000..a2a43e65b9fc --- /dev/null +++ b/misc/github-copilot-cli/pkg-plist.binary @@ -0,0 +1 @@ +bin/copilothome | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69c5a330.31ed3.6b679361>
