1111# Example: ./migrate-npm-packages-between-github-instances.sh joshjohanning-org github.com joshjohanning-emu github.com | tee output.log
1212#
1313# Notes:
14- # - Mapping the npm package to a repo is optional.
14+ # - Mapping the npm package to a repo is optional.
1515# - If there is a repo that exists in the target with the same repo name, it will map it
1616# - If the repo doesn't exist, the package will still import but won't be mapped to a repo
1717# - See ./failed-packages.txt for any packages that failed to import
1818
1919set -e
2020
21- if [ $# -ne " 4" ]; then
22- echo " Usage: $0 <source-org> <source-host> <target-org> <target-host>"
23- exit 1
21+ if [ $# -lt " 4" ]; then
22+ echo " Usage: $0 <source-org> <source-host> <target-org> <target-host>"
23+ exit 1
2424fi
2525
2626# make sure env variables are defined
2727if [ -z " $GH_SOURCE_PAT " ]; then
28- echo " Error: set GH_SOURCE_PAT env var"
29- exit 1
28+ echo " Error: set GH_SOURCE_PAT env var"
29+ exit 1
3030fi
3131
3232if [ -z " $GH_TARGET_PAT " ]; then
33- echo " Error: set GH_TARGET_PAT env var"
34- exit 1
33+ echo " Error: set GH_TARGET_PAT env var"
34+ exit 1
3535fi
3636
3737echo " ..."
@@ -40,14 +40,19 @@ SOURCE_ORG=$1
4040SOURCE_HOST=$2
4141TARGET_ORG=$3
4242TARGET_HOST=$4
43+ # Optional: Set CUTOFF_DATE to migrate only package versions created on or after this date.
44+ # Format: YYYY-MM-DDTHH:MM:SSZ (e.g., 2023-03-13T00:00:00Z)
45+ # Example: To migrate versions from the past year, use: $(date -u -v-1y +"%Y-%m-%dT%H:%M:%SZ")
46+ # If CUTOFF_DATE is unset or empty, all available versions will be migrated.
47+ CUTOFF_DATE=$5
4348
4449# create temp dir
4550mkdir -p ./temp
4651cd ./temp
4752temp_dir=$( pwd)
4853
4954# set up .npmrc for target org
50- echo @$TARGET_ORG :registry=https://npm.pkg.$TARGET_HOST / > $temp_dir /.npmrc && echo " //npm.pkg.$TARGET_HOST /:_authToken=$GH_TARGET_PAT " >> $temp_dir /.npmrc
55+ echo @$TARGET_ORG :registry=https://npm.pkg.$TARGET_HOST / > $temp_dir /.npmrc && echo " //npm.pkg.$TARGET_HOST /:_authToken=$GH_TARGET_PAT " >> $temp_dir /.npmrc
5156
5257packages=$( GH_HOST=" $SOURCE_HOST " GH_TOKEN=$GH_SOURCE_PAT gh api --paginate " /orgs/$SOURCE_ORG /packages?package_type=npm" -q ' .[] | .name + " " + .repository.name' )
5358
@@ -57,32 +62,44 @@ echo "$packages" | while IFS= read -r response; do
5762 repo_name=$( echo " $response " | cut -d ' ' -f 2)
5863
5964 echo " org: $SOURCE_ORG repo: $repo_name --> package name $package_name "
60-
61- versions=$( GH_HOST=" $SOURCE_HOST " GH_TOKEN=$GH_SOURCE_PAT gh api --paginate " /orgs/$SOURCE_ORG /packages/npm/$package_name /versions" -q ' .[] | .name' | sort -V)
62- for version in $versions
63- do
65+
66+ # Cache package metadata to avoid multiple API calls for each version
67+ curl -H " Authorization: token $GH_SOURCE_PAT " -Ls " https://npm.pkg.github.com/@$SOURCE_ORG /$package_name " > " ${temp_dir} /${package_name} .json"
68+
69+ # Fetch versions, filter by date only if CUTOFF_DATE is set
70+ if [ -n " $CUTOFF_DATE " ]; then
71+ versions=$( GH_HOST=" $SOURCE_HOST " GH_TOKEN=$GH_SOURCE_PAT gh api --paginate " /orgs/$SOURCE_ORG /packages/npm/$package_name /versions" |
72+ jq -r --arg cutoff " $CUTOFF_DATE " ' .[] | select(.created_at >= $cutoff) | .name' |
73+ sort -V)
74+ else
75+ versions=$( GH_HOST=" $SOURCE_HOST " GH_TOKEN=$GH_SOURCE_PAT gh api --paginate " /orgs/$SOURCE_ORG /packages/npm/$package_name /versions" |
76+ jq -r ' .[].name' |
77+ sort -V)
78+ fi
79+
80+ for version in $versions ; do
6481 echo " $version "
6582
6683 # get url of tarball
67- url=$( curl -H " Authorization: token $GH_SOURCE_PAT " -Ls https://npm.pkg.github.com/@ $SOURCE_ORG / $package_name | jq --arg version $version -r ' .versions[$version].dist.tarball' )
84+ url=$( jq --arg version $version -r ' .versions[$version].dist.tarball' " ${temp_dir} / ${package_name} .json " )
6885
6986 # check for error
7087 if [ " $url " == " null" ]; then
71- echo " ERROR: version $version not found for package $package_name "
72- echo " NOTE: Make sure you have the proper scopes for gh; ie run this: gh auth refresh -h github.com -s read:packages"
73- exit 1
88+ echo " ERROR: version $version not found for package $package_name "
89+ echo " NOTE: Make sure you have the proper scopes for gh; ie run this: gh auth refresh -h github.com -s read:packages"
90+ continue
7491 fi
7592
76- # download
93+ # download
7794 curl -sS -H " Authorization: token $GH_SOURCE_PAT " -L -o $package_name -$version .tgz $url
78-
95+
7996 # untar
8097 mkdir -p ./$package_name -$version
8198 # if you run into permissions issue, add a `sudo` here
8299 tar xzf $package_name -$version .tgz -C $package_name -$version
83100 cd $package_name -$version /package
84101 perl -pi -e " s/$SOURCE_ORG /$TARGET_ORG /ig" package.json
85- npm publish --ignore-scripts --userconfig $temp_dir /.npmrc || echo " skipped package due to failure: $package_name -$version .tgz" >> ./failed-packages.txt
102+ npm publish --ignore-scripts --userconfig $temp_dir /.npmrc || echo " skipped package due to failure: $package_name -$version .tgz" >> ./failed-packages.txt
86103 cd ./../../
87104
88105 done
0 commit comments