Skip to content

Commit bbcd7f9

Browse files
committed
fix: validate registry URL, avoid param mutation, fix private registry docs
- Validate registry-url upfront with new URL() and throw a clear error for invalid URLs - Normalize URL into a new variable instead of mutating the parameter - Remove separate scope mutation chain — use scopePrefix local variable - Fix README example: add run-install: false when using registry-url to prevent auto-install from 401ing with dummy token on private packages
1 parent af16239 commit bbcd7f9

File tree

4 files changed

+26
-22
lines changed

4 files changed

+26
-22
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ steps:
8282
8383
### With Private Registry (GitHub Packages)
8484
85+
When using `registry-url`, set `run-install: false` and run install manually with the auth token, otherwise the default auto-install will fail for private packages.
86+
8587
```yaml
8688
steps:
8789
- uses: actions/checkout@v6
@@ -90,6 +92,7 @@ steps:
9092
node-version: "22"
9193
registry-url: "https://npm.pkg.github.com"
9294
scope: "@myorg"
95+
run-install: false
9396
- run: vp install
9497
env:
9598
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

dist/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,4 @@ ${e.format(t)}
211211
`));let i=t.split(`
212212
`).map(e=>e.trim());for(let e of i)if(!e||e.startsWith(`#`))continue;else r.patterns.push(new m.Pattern(e));return r.searchPaths.push(...f.getSearchPaths(r.patterns)),r})}static stat(e,t,n){return i(this,void 0,void 0,function*(){let r;if(t.followSymbolicLinks)try{r=yield l.promises.stat(e.path)}catch(n){if(n.code===`ENOENT`){if(t.omitBrokenSymbolicLinks){c.debug(`Broken symlink '${e.path}'`);return}throw Error(`No information found for the path '${e.path}'. This may indicate a broken symbolic link.`)}throw n}else r=yield l.promises.lstat(e.path);if(r.isDirectory()&&t.followSymbolicLinks){let t=yield l.promises.realpath(e.path);for(;n.length>=e.level;)n.pop();if(n.some(e=>e===t)){c.debug(`Symlink cycle detected for path '${e.path}' and realpath '${t}'`);return}n.push(t)}return r})}}})),dm=v((e=>{var t=e&&e.__createBinding||(Object.create?(function(e,t,n,r){r===void 0&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);(!i||(`get`in i?!t.__esModule:i.writable||i.configurable))&&(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}):(function(e,t,n,r){r===void 0&&(r=n),e[r]=t[n]})),n=e&&e.__setModuleDefault||(Object.create?(function(e,t){Object.defineProperty(e,`default`,{enumerable:!0,value:t})}):function(e,t){e.default=t}),r=e&&e.__importStar||(function(){var e=function(t){return e=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},e(t)};return function(r){if(r&&r.__esModule)return r;var i={};if(r!=null)for(var a=e(r),o=0;o<a.length;o++)a[o]!==`default`&&t(i,r,a[o]);return n(i,r),i}})(),i=e&&e.__awaiter||function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},a=e&&e.__asyncValues||function(e){if(!Symbol.asyncIterator)throw TypeError(`Symbol.asyncIterator is not defined.`);var t=e[Symbol.asyncIterator],n;return t?t.call(e):(e=typeof __values==`function`?__values(e):e[Symbol.iterator](),n={},r(`next`),r(`throw`),r(`return`),n[Symbol.asyncIterator]=function(){return this},n);function r(t){n[t]=e[t]&&function(n){return new Promise(function(r,a){n=e[t](n),i(r,a,n.done,n.value)})}}function i(e,t,n,r){Promise.resolve(r).then(function(t){e({value:t,done:n})},t)}};Object.defineProperty(e,`__esModule`,{value:!0}),e.hashFiles=f;let o=r(S(`crypto`)),s=r(nm()),c=r(S(`fs`)),l=r(S(`stream`)),u=r(S(`util`)),d=r(S(`path`));function f(e,t){return i(this,arguments,void 0,function*(e,t,n=!1){var r,i,f,p;let m=n?s.info:s.debug,h=!1,g=t||(process.env.GITHUB_WORKSPACE??process.cwd()),_=o.createHash(`sha256`),v=0;try{for(var y=!0,b=a(e.globGenerator()),x;x=yield b.next(),r=x.done,!r;y=!0){p=x.value,y=!1;let e=p;if(m(e),!e.startsWith(`${g}${d.sep}`)){m(`Ignore '${e}' since it is not under GITHUB_WORKSPACE.`);continue}if(c.statSync(e).isDirectory()){m(`Skip directory '${e}'.`);continue}let t=o.createHash(`sha256`);yield u.promisify(l.pipeline)(c.createReadStream(e),t),_.write(t.digest()),v++,h||=!0}}catch(e){i={error:e}}finally{try{!y&&!r&&(f=b.return)&&(yield f.call(b))}finally{if(i)throw i.error}}return _.end(),h?(m(`Found ${v} files to hash.`),_.digest(`hex`)):(m(`No matches found for glob`),``)})}})),fm=v((e=>{var t=e&&e.__awaiter||function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})};Object.defineProperty(e,`__esModule`,{value:!0}),e.hashFiles=a;let n=um(),r=dm();function i(e,r){return t(this,void 0,void 0,function*(){return yield n.DefaultGlobber.create(e,r)})}function a(e){return t(this,arguments,void 0,function*(e,t=``,n,a=!1){let o=!0;n&&typeof n.followSymbolicLinks==`boolean`&&(o=n.followSymbolicLinks);let s=yield i(e,{followSymbolicLinks:o});return(0,r.hashFiles)(s,t,a)})}}))();async function pm(e){let t=Vd(e.cacheDependencyPath);if(!t){(0,$.warning)(`No lock file found. Skipping cache restore.`),(0,$.setOutput)(Li.CacheHit,!1);return}(0,$.info)(`Using lock file: ${t.path}`);let n=await Ud(t.type);if(!n.length){(0,$.warning)(`No cache directories found. Skipping cache restore.`),(0,$.setOutput)(Li.CacheHit,!1);return}(0,$.debug)(`Cache paths: ${n.join(`, `)}`),(0,$.saveState)(Ii.CachePaths,JSON.stringify(n));let r=process.env.RUNNER_OS||c(),i=o(),a=await(0,fm.hashFiles)(t.path);if(!a)throw Error(`Failed to generate hash for lock file: ${t.path}`);let s=`vite-plus-${r}-${i}-${t.type}-${a}`,l=[`vite-plus-${r}-${i}-${t.type}-`,`vite-plus-${r}-${i}-`];(0,$.debug)(`Primary key: ${s}`),(0,$.debug)(`Restore keys: ${l.join(`, `)}`),(0,$.saveState)(Ii.CachePrimaryKey,s);let u=await(0,Id.restoreCache)(n,s,l);u?((0,$.info)(`Cache restored from key: ${u}`),(0,$.saveState)(Ii.CacheMatchedKey,u),(0,$.setOutput)(Li.CacheHit,!0)):((0,$.info)(`Cache not found`),(0,$.setOutput)(Li.CacheHit,!1))}async function mm(){let e=(0,$.getState)(Ii.CachePrimaryKey),t=(0,$.getState)(Ii.CacheMatchedKey),n=(0,$.getState)(Ii.CachePaths);if(!e){(0,$.info)(`No cache key found. Skipping cache save.`);return}if(!n){(0,$.info)(`No cache paths found. Skipping cache save.`);return}if(e===t){(0,$.info)(`Cache hit on primary key "${e}". Skipping save.`);return}let r=JSON.parse(n);if(!r.length){(0,$.info)(`Empty cache paths. Skipping cache save.`);return}try{if(await(0,Id.saveCache)(r,e)===-1){(0,$.warning)(`Cache save failed or was skipped.`);return}(0,$.info)(`Cache saved with key: ${e}`)}catch(e){(0,$.warning)(`Failed to save cache: ${String(e)}`)}}function hm(e){let n=zd(e),r;try{r=u(n,`utf-8`)}catch{throw Error(`node-version-file not found: ${n}`)}let i=t(n),a;if(a=i===`.tool-versions`?vm(r):i===`package.json`?bm(r):gm(r),!a)throw Error(`No Node.js version found in ${e}`);return a=a.replace(/^v/i,``),(0,$.info)(`Resolved Node.js version '${a}' from ${e}`),a}function gm(e){for(let t of e.split(`
213213
`)){let e=(t.includes(`#`)?t.slice(0,t.indexOf(`#`)):t).trim();if(e)return _m(e)}}function _m(e){let t=e.toLowerCase();return t===`node`||t===`stable`?`latest`:e}function vm(e){for(let t of e.split(`
214-
`)){let e=t.trim();if(!e||e.startsWith(`#`))continue;let[n,...r]=e.split(/\s+/);if(!(n!==`nodejs`&&n!==`node`)){for(let e of r)if(ym(e))return e}}}function ym(e){return!!e&&e!==`system`&&!e.startsWith(`ref:`)&&!e.startsWith(`path:`)}function bm(e){let t;try{t=JSON.parse(e)}catch{throw Error(`Failed to parse package.json: invalid JSON`)}let n=t.devEngines;if(n?.runtime){let e=xm(n.runtime);if(e)return e}let r=t.engines;if(r?.node&&typeof r.node==`string`)return r.node}function xm(e){let t=Array.isArray(e)?e:[e];for(let e of t)if(e?.name===`node`&&typeof e.version==`string`)return e.version}function Sm(e,t){let n=i(process.env.RUNNER_TEMP||process.cwd(),`.npmrc`);e.endsWith(`/`)||(e+=`/`),Cm(e,n,t)}function Cm(e,t,n){if(!n)try{new URL(e).hostname===`npm.pkg.github.com`&&(n=process.env.GITHUB_REPOSITORY_OWNER)}catch{}n&&!n.startsWith(`@`)&&(n=`@`+n),n=n?n.toLowerCase()+`:`:``,(0,$.debug)(`Setting auth in ${t}`);let r=e.replace(/^\w+:/,``).toLowerCase(),i=[];if(l(t)){let e=u(t,`utf8`);for(let t of e.split(/\r?\n/)){let e=t.toLowerCase();e.startsWith(`${n}registry`)||e.startsWith(r)&&e.includes(`_authtoken`)||i.push(t)}}let o=e.replace(/^\w+:/,``)+":_authToken=${NODE_AUTH_TOKEN}",s=`${n}registry=${e}`;i.push(o,s),f(t,i.join(a)),(0,$.exportVariable)(`NPM_CONFIG_USERCONFIG`,t),(0,$.exportVariable)(`NODE_AUTH_TOKEN`,process.env.NODE_AUTH_TOKEN||`XXXXX-XXXXX-XXXXX-XXXXX`)}async function wm(e){(0,$.saveState)(Ii.IsPost,`true`);let t=e.nodeVersion;!t&&e.nodeVersionFile&&(t=hm(e.nodeVersionFile)),await Xd(e,t||``),t&&((0,$.info)(`Setting up Node.js ${t} via vp env use...`),await(0,Mi.exec)(`vp`,[`env`,`use`,t])),e.registryUrl&&Sm(e.registryUrl,e.scope),e.cache&&await pm(e),e.runInstall.length>0&&await Qd(e),await Tm()}async function Tm(){try{let e=(await(0,Mi.getExecOutput)(`vp`,[`--version`],{silent:!0})).stdout.trim();(0,$.info)(e);let t=e.match(/Global:\s*v?([\d.]+[^\s]*)/i)?.[1]||`unknown`;(0,$.saveState)(Ii.InstalledVersion,t),(0,$.setOutput)(Li.Version,t)}catch(e){(0,$.warning)(`Could not get vp version: ${String(e)}`),(0,$.setOutput)(Li.Version,`unknown`)}}async function Em(e){let t=[Yd()];e.cache&&t.push(mm()),await Promise.all(t)}async function Dm(){let e=zi();(0,$.getState)(Ii.IsPost)===`true`?await Em(e):await wm(e)}Dm().catch(e=>{console.error(e),(0,$.setFailed)(e instanceof Error?e.message:String(e))});export{};
214+
`)){let e=t.trim();if(!e||e.startsWith(`#`))continue;let[n,...r]=e.split(/\s+/);if(!(n!==`nodejs`&&n!==`node`)){for(let e of r)if(ym(e))return e}}}function ym(e){return!!e&&e!==`system`&&!e.startsWith(`ref:`)&&!e.startsWith(`path:`)}function bm(e){let t;try{t=JSON.parse(e)}catch{throw Error(`Failed to parse package.json: invalid JSON`)}let n=t.devEngines;if(n?.runtime){let e=xm(n.runtime);if(e)return e}let r=t.engines;if(r?.node&&typeof r.node==`string`)return r.node}function xm(e){let t=Array.isArray(e)?e:[e];for(let e of t)if(e?.name===`node`&&typeof e.version==`string`)return e.version}function Sm(e,t){let n;try{n=new URL(e)}catch{throw Error(`Invalid registry-url: "${e}". Must be a valid URL.`)}Cm(n.href.endsWith(`/`)?n.href:n.href+`/`,i(process.env.RUNNER_TEMP||process.cwd(),`.npmrc`),t)}function Cm(e,t,n){n||new URL(e).hostname===`npm.pkg.github.com`&&(n=process.env.GITHUB_REPOSITORY_OWNER);let r=``;n&&(r=(n.startsWith(`@`)?n:`@`+n).toLowerCase()+`:`),(0,$.debug)(`Setting auth in ${t}`);let i=e.replace(/^\w+:/,``).toLowerCase(),o=[];if(l(t)){let e=u(t,`utf8`);for(let t of e.split(/\r?\n/)){let e=t.toLowerCase();e.startsWith(`${r}registry`)||e.startsWith(i)&&e.includes(`_authtoken`)||o.push(t)}}let s=e.replace(/^\w+:/,``)+":_authToken=${NODE_AUTH_TOKEN}",c=`${r}registry=${e}`;o.push(s,c),f(t,o.join(a)),(0,$.exportVariable)(`NPM_CONFIG_USERCONFIG`,t),(0,$.exportVariable)(`NODE_AUTH_TOKEN`,process.env.NODE_AUTH_TOKEN||`XXXXX-XXXXX-XXXXX-XXXXX`)}async function wm(e){(0,$.saveState)(Ii.IsPost,`true`);let t=e.nodeVersion;!t&&e.nodeVersionFile&&(t=hm(e.nodeVersionFile)),await Xd(e,t||``),t&&((0,$.info)(`Setting up Node.js ${t} via vp env use...`),await(0,Mi.exec)(`vp`,[`env`,`use`,t])),e.registryUrl&&Sm(e.registryUrl,e.scope),e.cache&&await pm(e),e.runInstall.length>0&&await Qd(e),await Tm()}async function Tm(){try{let e=(await(0,Mi.getExecOutput)(`vp`,[`--version`],{silent:!0})).stdout.trim();(0,$.info)(e);let t=e.match(/Global:\s*v?([\d.]+[^\s]*)/i)?.[1]||`unknown`;(0,$.saveState)(Ii.InstalledVersion,t),(0,$.setOutput)(Li.Version,t)}catch(e){(0,$.warning)(`Could not get vp version: ${String(e)}`),(0,$.setOutput)(Li.Version,`unknown`)}}async function Em(e){let t=[Yd()];e.cache&&t.push(mm()),await Promise.all(t)}async function Dm(){let e=zi();(0,$.getState)(Ii.IsPost)===`true`?await Em(e):await wm(e)}Dm().catch(e=>{console.error(e),(0,$.setFailed)(e instanceof Error?e.message:String(e))});export{};

src/auth.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ describe("configAuthentication", () => {
150150
expect(written).not.toContain("@voidzero-dev:");
151151
});
152152

153+
it("should throw on invalid URL", () => {
154+
expect(() => configAuthentication("not-a-url")).toThrow("Invalid registry-url");
155+
});
156+
153157
it("should export NPM_CONFIG_USERCONFIG", () => {
154158
configAuthentication("https://registry.npmjs.org/");
155159

src/auth.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,33 @@ import { debug, exportVariable } from "@actions/core";
88
* Ported from actions/setup-node's authutil.ts.
99
*/
1010
export function configAuthentication(registryUrl: string, scope?: string): void {
11-
const npmrc = resolve(process.env.RUNNER_TEMP || process.cwd(), ".npmrc");
12-
13-
if (!registryUrl.endsWith("/")) {
14-
registryUrl += "/";
11+
// Validate and normalize the registry URL
12+
let url: URL;
13+
try {
14+
url = new URL(registryUrl);
15+
} catch {
16+
throw new Error(`Invalid registry-url: "${registryUrl}". Must be a valid URL.`);
1517
}
1618

17-
writeRegistryToFile(registryUrl, npmrc, scope);
19+
// Ensure trailing slash
20+
const normalizedUrl = url.href.endsWith("/") ? url.href : url.href + "/";
21+
const npmrc = resolve(process.env.RUNNER_TEMP || process.cwd(), ".npmrc");
22+
23+
writeRegistryToFile(normalizedUrl, npmrc, scope);
1824
}
1925

2026
function writeRegistryToFile(registryUrl: string, fileLocation: string, scope?: string): void {
2127
// Auto-detect scope for GitHub Packages registry using exact host match
2228
if (!scope) {
23-
try {
24-
const url = new URL(registryUrl);
25-
if (url.hostname === "npm.pkg.github.com") {
26-
scope = process.env.GITHUB_REPOSITORY_OWNER;
27-
}
28-
} catch {
29-
// Invalid URL — skip auto-detection
29+
const url = new URL(registryUrl);
30+
if (url.hostname === "npm.pkg.github.com") {
31+
scope = process.env.GITHUB_REPOSITORY_OWNER;
3032
}
3133
}
3234

33-
if (scope && !scope.startsWith("@")) {
34-
scope = "@" + scope;
35-
}
36-
35+
let scopePrefix = "";
3736
if (scope) {
38-
scope = scope.toLowerCase() + ":";
39-
} else {
40-
scope = "";
37+
scopePrefix = (scope.startsWith("@") ? scope : "@" + scope).toLowerCase() + ":";
4138
}
4239

4340
debug(`Setting auth in ${fileLocation}`);
@@ -51,15 +48,15 @@ function writeRegistryToFile(registryUrl: string, fileLocation: string, scope?:
5148
for (const line of curContents.split(/\r?\n/)) {
5249
const lower = line.toLowerCase();
5350
// Remove existing registry and auth token lines for this scope/registry
54-
if (lower.startsWith(`${scope}registry`)) continue;
51+
if (lower.startsWith(`${scopePrefix}registry`)) continue;
5552
if (lower.startsWith(authPrefix) && lower.includes("_authtoken")) continue;
5653
lines.push(line);
5754
}
5855
}
5956

6057
// Auth token line: remove protocol prefix from registry URL
6158
const authString = registryUrl.replace(/^\w+:/, "") + ":_authToken=${NODE_AUTH_TOKEN}";
62-
const registryString = `${scope}registry=${registryUrl}`;
59+
const registryString = `${scopePrefix}registry=${registryUrl}`;
6360
lines.push(authString, registryString);
6461

6562
writeFileSync(fileLocation, lines.join(EOL));

0 commit comments

Comments
 (0)