-
SummaryI want to use private repo in my nextjs app. Docs (https://vercel.com/guides/using-private-dependencies-with-vercel) are saying that I need to use BitBucket app password, which is now deprecated. From BitBucket docs (link: https://support.atlassian.com/bitbucket-cloud/docs/revoke-an-app-password/)
I tried to create API Token with scope and it works locally but then fails at Vercel's CI. Anyone managed to get Vercel’s CI working with API Tokens and pnpm? If so, how? Additional informationNo response ExampleNo response |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 6 replies
-
@icyJoseph sorry for tagging you but can someone from Vercel take a look at this? |
Beta Was this translation helpful? Give feedback.
-
Hey @icyJoseph I've managed to fix the issue. The problem is that with current pnpm version (v10.17.0 at time of writing) even if I add something like in my "my-repo": "git+https://x-token-auth:<access-token>@bitbucket.org/<my-org>/<my-repo>.git"" I end up with following entries in my-repo@git+https://git@bitbucket.org:my-org/my-repo.git#1e37d6cc8bb4a22a897b01774e6ffd04a8a2617b:
resolution: {commit: 1e37d6cc8bb4a22a897b01774e6ffd04a8a2617b, repo: git@bitbucket.org:my-org/my-repo.git, type: git}
version: 0.1.0
engines: {node: '>= 20.x.x'}
my-repo@git+https://git@bitbucket.org:my-org/my-repo.git#1e37d6cc8bb4a22a897b01774e6ffd04a8a2617b:
dependencies:
date-fns: 4.1.0
date-fns-tz: 3.2.0(date-fns@4.1.0)
lodash: 4.17.21 I'm assuming that by default pnpm "prefers" to use SSH. Since locally I have my SSH keys configured, it works without any issues and pnpm is able to clone the repo which is then registered in the lockfile with SSH URL. This happens even if I explicitly add the repo with HTTPS URL. This is problematic in 2 ways:
The solution I've come up with is to use This works locally and in Vercel's CI/CD. In the scenario that I'm working on, our NextJs-based project depends on a private Bitbucket repo called The only thing that need to be set in the environment are the access tokens for both repos:
Code: const { execSync } = require("child_process");
const ORG_NAME = "my-org";
const MAIN_DEPENDENCY_PACKAGE_NAME = "main-dependency-package";
const SUB_DEPENDENCY_PACKAGE_NAME = "sub-dependency-package";
const MAIN_DEPENDENCY_TOKEN = process.env.MAIN_DEPENDENCY_TOKEN;
const SUB_DEPENDENCY_TOKEN = process.env.SUB_DEPENDENCY_TOKEN;
const MAIN_DEPENDENCY_REPO_URL = `https://x-token-auth:${encodeURIComponent(
MAIN_DEPENDENCY_TOKEN
)}@bitbucket.org/${ORG_NAME}/${MAIN_DEPENDENCY_PACKAGE_NAME}.git`;
const SUB_DEPENDENCY_REPO_URL = `https://x-token-auth:${encodeURIComponent(
SUB_DEPENDENCY_TOKEN
)}@bitbucket.org/${ORG_NAME}/${SUB_DEPENDENCY_PACKAGE_NAME}.git`;
/**
* Check if we can access a Bitbucket repository via SSH
*
* @param {Object} params
* @param {string} params.orgName
* @param {string} params.repoName
* @returns
*/
function statBitbucketRepo({ orgName, repoName }) {
try {
const command = `git ls-remote git@bitbucket.org:${orgName}/${repoName}.git`;
execSync(command, { stdio: "ignore" });
return true;
} catch {
return false;
}
}
/**
* Check if we can clone the Git repositories via SSH
* @returns {boolean}
*/
function canGitClone() {
const canAccessMainDep = statBitbucketRepo({
orgName: ORG_NAME,
repoName: MAIN_DEPENDENCY_PACKAGE_NAME,
});
const canAccessSubDep = statBitbucketRepo({
orgName: ORG_NAME,
repoName: SUB_DEPENDENCY_PACKAGE_NAME,
});
return canAccessMainDep && canAccessSubDep;
}
/**
* Pnpm preResolution hook to modify dependencies in the lockfile
* to use HTTPS with access tokens instead of SSH.
*
* Docs: https://pnpm.io/pnpmfile#hookspreresolutionoptions-promisevoid
*
* @param {Object} options
* @param {Object} options.wantedLockfile - The current lockfile
* @returns {Object} The modified options
*/
function preResolution(options) {
const lockfile = options.wantedLockfile;
if (!lockfile) return options;
if (!lockfile.packages) return options;
if (canGitClone()) {
console.log(
`Can access "${MAIN_DEPENDENCY_PACKAGE_NAME}" and "${SUB_DEPENDENCY_PACKAGE_NAME}" via SSH. No patching needed.`
);
return options;
}
console.log(
`Patching "${MAIN_DEPENDENCY_PACKAGE_NAME}" and "${SUB_DEPENDENCY_PACKAGE_NAME}" to use HTTPS with access tokens.`
);
if (!MAIN_DEPENDENCY_TOKEN)
throw new Error("MAIN_DEPENDENCY_TOKEN is not set");
if (!SUB_DEPENDENCY_TOKEN) throw new Error("SUB_DEPENDENCY_TOKEN is not set");
// First, we need to modify the sub-dependency, because the main dependency will reference it
const subDep = modifyDependency({
packages: lockfile.packages,
mainPackageName: SUB_DEPENDENCY_PACKAGE_NAME,
mainPackageRepoUrl: SUB_DEPENDENCY_REPO_URL,
});
if (subDep) {
const { newKey, oldKey, pkg } = subDep;
delete lockfile.packages[oldKey];
lockfile.packages[newKey] = pkg;
// console.log({ oldKey, newKey, pkg });
}
const mainDep = modifyDependency({
packages: lockfile.packages,
mainPackageName: MAIN_DEPENDENCY_PACKAGE_NAME,
mainPackageRepoUrl: MAIN_DEPENDENCY_REPO_URL,
subDepPackageName: SUB_DEPENDENCY_PACKAGE_NAME,
subDepPackageRepoUrl: SUB_DEPENDENCY_REPO_URL,
});
if (mainDep) {
const { oldKey, pkg } = mainDep;
lockfile.packages[oldKey] = pkg;
// console.log({ oldKey, pkg });
}
console.log("Patching done.");
return options;
}
/**
* Modify a dependency in the lockfile to use HTTPS with access token
* instead of SSH.
*
* @param {Object} params
* @param {Record<string, PnpmLockfileEntry>} params.packages
* @param {string} params.mainPackageName - e.g. `main-dependency-package`
* @param {string} params.mainPackageRepoUrl - e.g. `https://x-token-auth:...@bitbucket.org/my-org/main-dependency-package.git`
* @param {string} [params.subDepPackageName] - e.g. `sub-dependency-package`
* @param {string} [params.subDepPackageRepoUrl] - e.g. `https://x-token-auth:...@bitbucket.org/my-org/sub-dependency-package.git`
* @returns {Object|null} - Returns an object with oldKey, newKey and modified pkg, or null if not found
*/
function modifyDependency({
packages,
mainPackageName,
mainPackageRepoUrl,
subDepPackageName,
subDepPackageRepoUrl,
}) {
const key = getPackageNameFromKey({
packages,
packageName: mainPackageName,
});
if (!key) return null;
/** @type {PnpmLockfileEntry} */
const pkg = packages[key];
if (!pkg) return null;
const newKey = rewriteDependencyKey({
key: key,
packageName: mainPackageName,
repoUrl: mainPackageRepoUrl,
});
pkg.resolution.repo = mainPackageRepoUrl;
if (
subDepPackageName &&
pkg.dependencies &&
pkg.dependencies[subDepPackageName]
) {
const from = pkg.dependencies[subDepPackageName];
const [_, commit] = from.split("#");
pkg.dependencies[
subDepPackageName
] = `git+${subDepPackageRepoUrl}#${commit}`;
}
return { oldKey: key, newKey, pkg };
}
/**
* @param {Object} params
* @param {Record<string, PnpmLockfileEntry>} params.packages
* @param {string} params.packageName
* @returns
*/
function getPackageNameFromKey({ packages, packageName }) {
const key = Object.keys(packages).find((k) => k.includes(packageName));
return key ? key : null;
}
/**
* Rewrite a dependency key to use HTTPS with access token instead of SSH.
* Example:
* ```js
* const input ='repo-name@git+https://git@bitbucket.org:org-name/repo-name.git#<COMMIT>'
* const output = 'repo-name@git+https://x-token-auth:<REPO-TOKEN>@bitbucket.org/my-org/repo-name.git#<COMMIT>'
* ```
* @param {Object} params
* @param {string} params.key - lockfile key that identifies a package
* @param {string} params.packageName - e.g. `main-dependency-package`
* @param {string} params.repoUrl - HTTP that includes `x-token-auth:...`
*/
function rewriteDependencyKey({ key, packageName, repoUrl }) {
const [name] = key.split("@");
const [_, commit] = key.split("#");
if (name !== packageName) return key;
const newKey = commit
? `${name}@git+${repoUrl}#${commit}`
: `${name}@git+${repoUrl}`;
return newKey;
}
module.exports = {
hooks: {
preResolution,
},
};
/**
* @typedef PnpmLockfileEntry
* @property {string} version
* @property {Record<string, string>} [dependencies]
* @property {Object} resolution
* @property {string} resolution.commit
* @property {string} resolution.repo
* @property {string} resolution.type
*/ |
Beta Was this translation helpful? Give feedback.
Hey @icyJoseph I've managed to fix the issue.
The problem is that with current pnpm version (v10.17.0 at time of writing) even if I add something like in my
package.json
I end up with following entries in
pnpm-lock.yaml