@preventive/upstream 1.0.0-alpha.5 → 1.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/npm.d.ts CHANGED
@@ -6,6 +6,9 @@
6
6
  // cache path, otherwise. Responses are read up to a size limit and within
7
7
  // a timeout, as github.d.ts describes, and lookups for many names go to
8
8
  // the registry eight at a time.
9
+ //
10
+ // Where NPM_TOKEN is set, it is sent with the requests for a scoped
11
+ // package, so a private one can be read.
9
12
 
10
13
  // Where cached answers live, resolved when set. Unset by default, and
11
14
  // unset means no cache: every read misses and every write is skipped.
@@ -47,7 +50,8 @@ export interface PackageRepo {
47
50
  export function resolvePackageRepos(packageNames: Iterable<string>, options?: { cachedOnly?: boolean }): Promise<Map<string, PackageRepo>>
48
51
 
49
52
  // The disk cache resolvePackageRepos reads and writes, a month per entry.
50
- // A read answers null for anything but a usable entry; a write answers
53
+ // A read answers null for anything but a usable entry, one written by a
54
+ // release that resolved repos by other rules included; a write answers
51
55
  // false where it could not write, and never throws.
52
56
  export function readPackageRepoCache(name: string): Promise<PackageRepo | null>
53
57
  export function writePackageRepoCache(name: string, github: string, directory?: string): Promise<boolean>
package/package.d.ts CHANGED
@@ -11,7 +11,7 @@ export interface PackageRepoLink {
11
11
  url?: string
12
12
  }
13
13
 
14
- // Read off `bugs`, `repository` and `homepage`, in that order, from a
14
+ // Read off `repository`, `bugs` and `homepage`, in that order, from a
15
15
  // package.json or the registry's document for a version of one. Both
16
16
  // fields absent where nothing in it names a GitHub repo; a throw only
17
17
  // for something that is not an object at all.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preventive/upstream",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "Minimal npm, crates.io and Soldeer registry clients, a GitHub client, and a git checkout reader: the calls we use, nothing more",
5
5
  "type": "module",
6
6
  "exports": {
@@ -78,6 +78,7 @@
78
78
  "src/github/read.js",
79
79
  "src/github/write.js",
80
80
  "src/http.js",
81
+ "src/npm/registry.js",
81
82
  "src/npm/repos.js",
82
83
  "src/npm/tarball.js",
83
84
  "src/package.js",
@@ -0,0 +1,19 @@
1
+ import assert from 'node:assert/strict'
2
+
3
+ import { show } from '../args.js'
4
+ import { NPM_REGISTRY, buildUrl, request } from '../http.js'
5
+
6
+ // Private packages are always scoped.
7
+ export function withNpmToken(name, url, options = {}) {
8
+ const method = options.method ?? 'GET'
9
+ assert.ok(method === 'GET' && `${new URL(url)}` === url && url.startsWith(`${NPM_REGISTRY}/${name}/`), `Unexpected request for ${name}: ${method} ${url}`)
10
+ const token = process.env.NPM_TOKEN
11
+ return token && name.startsWith('@') ? { ...options, method, headers: { ...options.headers, Authorization: `Bearer ${token}` } } : options
12
+ }
13
+
14
+ export async function getDocument(method, name, spec) {
15
+ const url = buildUrl(NPM_REGISTRY, [...name.split('/'), spec])
16
+ const json = await request(url, withNpmToken(name, url, { as: 'json' }))
17
+ assert.ok(json?.name === name, `${method}: the registry answered for ${show(json?.name)}, not ${name}`)
18
+ return json
19
+ }
package/src/npm/repos.js CHANGED
@@ -1,20 +1,20 @@
1
1
  import assert from 'node:assert/strict'
2
2
 
3
- import { assertNames, assertPackageName, assertRepo, isRepo, show } from '../args.js'
3
+ import { assertNames, assertPackageName, assertRepo, isRepo } from '../args.js'
4
4
  import { readRecord, writeRecord } from '../cache.js'
5
- import { NPM_REGISTRY, buildUrl, isNotFound, recover, request } from '../http.js'
5
+ import { isNotFound, recover } from '../http.js'
6
6
  import { assertRepoDirectory, getRepo, isRepoDirectory } from '../package.js'
7
7
  import { pool } from '../pool.js'
8
+ import { getDocument } from './registry.js'
8
9
 
9
10
  const DIR = 'npm/repos'
10
11
  const CONCURRENCY = 8
12
+ // Stamped on each entry, and raised when getRepo would answer differently:
13
+ // 2 is `repository` taking precedence over `bugs`.
14
+ const VERSION = 2
11
15
 
12
16
  // `latest`, not the full packument, which is megabytes of version history.
13
- async function fetchRepo(method, name) {
14
- const json = await request(buildUrl(NPM_REGISTRY, [...name.split('/'), 'latest']), { as: 'json' })
15
- assert.ok(json?.name === name, `${method}: the registry answered for ${show(json?.name)}, not ${name}`)
16
- return getRepo(json)
17
- }
17
+ const fetchRepo = async (method, name) => getRepo(await getDocument(method, name, 'latest'))
18
18
 
19
19
  export async function getGitHub(name) {
20
20
  assertPackageName('getGitHub', 'name', name)
@@ -23,12 +23,13 @@ export async function getGitHub(name) {
23
23
  return link
24
24
  }
25
25
 
26
- // An entry without `directory` predates the field: a miss, not a package
27
- // at the repo root.
26
+ // An entry under another VERSION was resolved by other rules, and may
27
+ // name the repo a stale tracker does: a miss, as is one without
28
+ // `directory`, which would read as a package at the repo root.
28
29
  export async function readPackageRepoCache(name) {
29
30
  assertPackageName('readPackageRepoCache', 'name', name)
30
31
  const entry = await readRecord(DIR, name)
31
- if (!isRepo(entry?.github) || !isRepoDirectory(entry.directory)) return null
32
+ if (entry?.v !== VERSION || !isRepo(entry.github) || !isRepoDirectory(entry.directory)) return null
32
33
  return { github: entry.github, ...(entry.directory && { directory: entry.directory }) }
33
34
  }
34
35
 
@@ -38,7 +39,7 @@ export async function writePackageRepoCache(name, github, directory = '') {
38
39
  assertPackageName('writePackageRepoCache', 'name', name)
39
40
  assertRepo('writePackageRepoCache', 'github', github)
40
41
  assertRepoDirectory('writePackageRepoCache', 'directory', directory)
41
- return await writeRecord(DIR, name, { github, directory })
42
+ return await writeRecord(DIR, name, { v: VERSION, github, directory })
42
43
  }
43
44
 
44
45
  export async function resolvePackageRepos(packageNames, options = {}) {
@@ -5,7 +5,8 @@ import { join, resolve } from 'node:path'
5
5
 
6
6
  import { assertArgs, assertPackage, assertion, matches, show } from '../args.js'
7
7
  import { verifiedDownload } from '../download.js'
8
- import { NPM_REGISTRY, buildUrl, request } from '../http.js'
8
+ import { NPM_REGISTRY, buildUrl } from '../http.js'
9
+ import { getDocument, withNpmToken } from './registry.js'
9
10
 
10
11
  const DIR = 'npm/tarballs' // No expiry: the registry never takes a version twice.
11
12
  // One sha512 and nothing else, as the registry writes it: a sha1, a second
@@ -29,8 +30,8 @@ function checkedDist(method, name, version, dist) {
29
30
  }
30
31
 
31
32
  async function getDist(method, name, version) {
32
- const json = await request(buildUrl(NPM_REGISTRY, [...name.split('/'), version]), { as: 'json' })
33
- assert.ok(json?.name === name && json.version === version, `${method}: the registry answered for ${show(json?.name)}@${show(json?.version)}, not ${name}@${version}`)
33
+ const json = await getDocument(method, name, version)
34
+ assert.ok(json.version === version, `${method}: the registry answered for ${name}@${show(json.version)}, not ${name}@${version}`)
34
35
  return checkedDist(method, name, version, { tarball: json.dist?.tarball, integrity: json.dist?.integrity })
35
36
  }
36
37
 
@@ -70,5 +71,5 @@ export async function verifyDist(name, version, dist) {
70
71
  export async function getTarball(name, version, dist) {
71
72
  assertPackage('getTarball', name, version)
72
73
  const { tarball, integrity } = dist === undefined ? await getDist('getTarball', name, version) : checkedDist('getTarball', name, version, dist)
73
- return await verifiedDownload({ method: 'getTarball', dir: DIR, what: `${name}@${version}`, ext: 'tgz', algorithm: 'sha512', expected: integrity, local: localPaths(name, version, integrity), locate: () => tarball })
74
+ return await verifiedDownload({ method: 'getTarball', dir: DIR, what: `${name}@${version}`, ext: 'tgz', algorithm: 'sha512', expected: integrity, local: localPaths(name, version, integrity), locate: () => tarball, options: withNpmToken(name, tarball) })
74
75
  }
package/src/package.js CHANGED
@@ -41,7 +41,9 @@ export function getRepo(pkg) {
41
41
  const { bugs, repository } = pkg
42
42
  const declared = repositoryRepo(urlOf(repository))
43
43
  const homepage = homepageRegex.exec(homepageUrl(pkg.homepage))?.groups
44
- const github = [repoIn(bugsRegex, urlOf(bugs)), declared, homepage?.repo].find(isRepo)
44
+ // `repository` first: it is the field a publisher maintains, where a
45
+ // `bugs` tracker can be left pointing at a former owner or misspelt.
46
+ const github = [declared, repoIn(bugsRegex, urlOf(bugs)), homepage?.repo].find(isRepo)
45
47
  if (github === undefined) return {}
46
48
  const directory = (sameName(declared, github) && repoSubdirectory(repository?.directory))
47
49
  || (sameName(homepage?.repo, github) && repoSubdirectory(decodePath(homepage.directory)))