diff --git a/.changeset/fluffy-olives-check.md b/.changeset/fluffy-olives-check.md new file mode 100644 index 0000000000..ce99d2b789 --- /dev/null +++ b/.changeset/fluffy-olives-check.md @@ -0,0 +1,5 @@ +--- +"@pnpm/local-resolver": minor +--- + +Allow to link a directory that has no manifest file. diff --git a/packages/local-resolver/src/index.ts b/packages/local-resolver/src/index.ts index 6f9e31e08a..1370a32a8e 100644 --- a/packages/local-resolver/src/index.ts +++ b/packages/local-resolver/src/index.ts @@ -1,3 +1,5 @@ +import { existsSync } from 'fs' +import path from 'path' import PnpmError from '@pnpm/error' import gfs from '@pnpm/graceful-fs' import { readProjectManifestOnly } from '@pnpm/read-project-manifest' @@ -50,14 +52,22 @@ export default async function resolveLocal ( try { localDependencyManifest = await readProjectManifestOnly(spec.fetchSpec) as DependencyManifest } catch (internalErr) { + if (!existsSync(spec.fetchSpec)) { + throw new PnpmError('LINKED_PKG_DIR_NOT_FOUND', + `Could not install from "${spec.fetchSpec}" as it does not exist.`) + } switch (internalErr.code) { case 'ENOTDIR': { throw new PnpmError('NOT_PACKAGE_DIRECTORY', `Could not install from "${spec.fetchSpec}" as it is not a directory.`) } + case 'ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND': case 'ENOENT': { - throw new PnpmError('DIRECTORY_HAS_NO_PACKAGE_JSON', - `Could not install from "${spec.fetchSpec}" as it does not contain a package.json file.`) + localDependencyManifest = { + name: path.basename(spec.fetchSpec), + version: '0.0.0', + } + break } default: { throw internalErr diff --git a/packages/local-resolver/test/index.ts b/packages/local-resolver/test/index.ts index 0ea058d7cf..e98148dac2 100644 --- a/packages/local-resolver/test/index.ts +++ b/packages/local-resolver/test/index.ts @@ -98,14 +98,11 @@ test('fail when resolving tarball specified with the link: protocol', async () = }) test('fail when resolving from not existing directory', async () => { - try { - const wantedDependency = { pref: 'link:./dir-does-not-exist' } - await resolveFromLocal(wantedDependency, { projectDir: __dirname }) - fail() - } catch (err) { - expect(err).toBeDefined() - expect(err.code).toEqual('ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND') - } + const wantedDependency = { pref: 'link:./dir-does-not-exist' } + const projectDir = __dirname + await expect( + resolveFromLocal(wantedDependency, { projectDir }) + ).rejects.toThrow(`Could not install from "${path.join(projectDir, 'dir-does-not-exist')}" as it does not exist.`) }) test('throw error when the path: protocol is used', async () => { diff --git a/packages/supi/test/install/local.ts b/packages/supi/test/install/local.ts index c433305ef3..1cd04aa0ac 100644 --- a/packages/supi/test/install/local.ts +++ b/packages/supi/test/install/local.ts @@ -47,6 +47,18 @@ test('local file', async () => { }) }) +test('local directory with no package.json', async () => { + const project = prepareEmpty() + await fs.mkdir('pkg') + await fs.writeFile('pkg/index.js', 'hello', 'utf8') + + const manifest = await addDependenciesToPackage({}, ['file:./pkg'], await testDefaults()) + + const expectedSpecs = { pkg: 'link:pkg' } + expect(manifest.dependencies).toStrictEqual(expectedSpecs) + await project.has('pkg') +}) + test('local file via link:', async () => { const project = prepareEmpty() await copyFixture('local-pkg', path.resolve('..', 'local-pkg'))