6ecb2c366f
Publish snapshot / snapshot (push) Successful in 1m2s
[fix] default parameter run records parameters in session for replay[fix] remove default parameters for several cubes
169 lines
6.5 KiB
TypeScript
169 lines
6.5 KiB
TypeScript
/**
|
|
* Resolution of the packages named in `cubePackages`.
|
|
*
|
|
* Builds real `node_modules` trees under os.tmpdir() rather than faking the
|
|
* filesystem: what is under test is Node's own resolution, including the
|
|
* symlink layout pnpm produces, and neither survives a mock.
|
|
*/
|
|
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { resolveCubePackages } from '../src/cubes/packages.js';
|
|
|
|
describe('resolveCubePackages', () => {
|
|
let tmpDir: string;
|
|
|
|
/** Writes a package into `<root>/node_modules/<name>`, cubes and all. */
|
|
const install = (
|
|
root: string,
|
|
name: string,
|
|
manifest: Record<string, unknown>,
|
|
cubeDirs: string[] = ['cubes']
|
|
) => {
|
|
const dir = path.join(root, 'node_modules', name);
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name, ...manifest }));
|
|
for (const cubeDir of cubeDirs) fs.mkdirSync(path.join(dir, cubeDir), { recursive: true });
|
|
return dir;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
tmpDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'nopy-packages-')));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it('resolves a scoped package to its cube directories', () => {
|
|
const dir = install(tmpDir, '@acme/cubes-net', { nopy: { cubes: ['./cubes'] } });
|
|
|
|
const { packages, errors } = resolveCubePackages([{ spec: '@acme/cubes-net', from: tmpDir }]);
|
|
|
|
expect(errors).toEqual([]);
|
|
expect(packages).toEqual([
|
|
{ name: '@acme/cubes-net', root: dir, dirs: [path.join(dir, 'cubes')] },
|
|
]);
|
|
});
|
|
|
|
it('resolves an unscoped package and every directory it declares', () => {
|
|
const dir = install(tmpDir, 'cubes-net', { nopy: { cubes: ['./cubes', './extra'] } }, [
|
|
'cubes',
|
|
'extra',
|
|
]);
|
|
|
|
const { packages, errors } = resolveCubePackages([{ spec: 'cubes-net', from: tmpDir }]);
|
|
|
|
expect(errors).toEqual([]);
|
|
expect(packages[0].dirs).toEqual([path.join(dir, 'cubes'), path.join(dir, 'extra')]);
|
|
});
|
|
|
|
it('resolves through a symlinked package directory, as pnpm installs it', () => {
|
|
// pnpm puts the real package under .pnpm and symlinks it into place, which
|
|
// is why the resolver reads package.json instead of scanning node_modules.
|
|
const store = path.join(tmpDir, 'store', 'cubes-net');
|
|
fs.mkdirSync(path.join(store, 'cubes'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(store, 'package.json'),
|
|
JSON.stringify({ name: 'cubes-net', nopy: { cubes: ['./cubes'] } })
|
|
);
|
|
fs.mkdirSync(path.join(tmpDir, 'node_modules'), { recursive: true });
|
|
fs.symlinkSync(store, path.join(tmpDir, 'node_modules', 'cubes-net'));
|
|
|
|
const { packages, errors } = resolveCubePackages([{ spec: 'cubes-net', from: tmpDir }]);
|
|
|
|
expect(errors).toEqual([]);
|
|
expect(packages[0].dirs).toEqual([path.join(tmpDir, 'node_modules', 'cubes-net', 'cubes')]);
|
|
});
|
|
|
|
it('resolves from the declaring config directory, not the working directory', () => {
|
|
// The package is installed next to the config that names it. Nothing at the
|
|
// process cwd can see it, which is the case a package named in
|
|
// ~/.nopyrc.json always hits.
|
|
const elsewhere = path.join(tmpDir, 'elsewhere');
|
|
fs.mkdirSync(elsewhere, { recursive: true });
|
|
install(elsewhere, 'cubes-net', { nopy: { cubes: ['./cubes'] } });
|
|
|
|
expect(resolveCubePackages([{ spec: 'cubes-net', from: elsewhere }]).errors).toEqual([]);
|
|
expect(resolveCubePackages([{ spec: 'cubes-net', from: tmpDir }]).errors).toHaveLength(1);
|
|
});
|
|
|
|
it('reports a package that is not installed', () => {
|
|
const { packages, errors } = resolveCubePackages([{ spec: '@acme/missing', from: tmpDir }]);
|
|
|
|
expect(packages).toEqual([]);
|
|
expect(errors[0]).toMatch(/'@acme\/missing' is not installed/);
|
|
expect(errors[0]).toContain(tmpDir);
|
|
});
|
|
|
|
it('reports a package.json that cannot be parsed', () => {
|
|
const dir = path.join(tmpDir, 'node_modules', 'broken');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
fs.writeFileSync(path.join(dir, 'package.json'), '{ not json');
|
|
|
|
const { errors } = resolveCubePackages([{ spec: 'broken', from: tmpDir }]);
|
|
|
|
expect(errors[0]).toMatch(/cannot read/);
|
|
});
|
|
|
|
it('reports a package that declares no cubes', () => {
|
|
install(tmpDir, 'plain', {});
|
|
install(tmpDir, 'empty', { nopy: { cubes: [] } });
|
|
install(tmpDir, 'wrong-type', { nopy: { cubes: 'cubes' } });
|
|
install(tmpDir, 'not-strings', { nopy: { cubes: [1] } });
|
|
|
|
const { packages, errors } = resolveCubePackages(
|
|
['plain', 'empty', 'wrong-type', 'not-strings'].map((spec) => ({ spec, from: tmpDir }))
|
|
);
|
|
|
|
expect(packages).toEqual([]);
|
|
expect(errors).toHaveLength(4);
|
|
for (const error of errors) expect(error).toMatch(/declares no cubes/);
|
|
});
|
|
|
|
it('reports a cube directory that does not exist', () => {
|
|
install(tmpDir, 'cubes-net', { nopy: { cubes: ['./nope'] } });
|
|
|
|
const { packages, errors } = resolveCubePackages([{ spec: 'cubes-net', from: tmpDir }]);
|
|
|
|
expect(packages).toEqual([]);
|
|
expect(errors[0]).toMatch(/'\.\/nope' does not exist/);
|
|
});
|
|
|
|
it('reports a cube directory that points outside the package', () => {
|
|
install(tmpDir, 'cubes-net', { nopy: { cubes: ['../../..'] } });
|
|
|
|
const { errors } = resolveCubePackages([{ spec: 'cubes-net', from: tmpDir }]);
|
|
|
|
expect(errors[0]).toMatch(/points outside the package/);
|
|
});
|
|
|
|
it('keeps the directories that are valid when a sibling entry is not', () => {
|
|
const dir = install(tmpDir, 'cubes-net', { nopy: { cubes: ['./cubes', './nope'] } });
|
|
|
|
const { packages, errors } = resolveCubePackages([{ spec: 'cubes-net', from: tmpDir }]);
|
|
|
|
expect(errors).toHaveLength(1);
|
|
expect(packages[0].dirs).toEqual([path.join(dir, 'cubes')]);
|
|
});
|
|
|
|
it('resolves a package named by two configs once, from the more specific one', () => {
|
|
// Merge order is root-first, so the last ref came from the config nearest
|
|
// the working directory — and only that one is guaranteed to resolve.
|
|
const child = path.join(tmpDir, 'child');
|
|
fs.mkdirSync(child, { recursive: true });
|
|
const dir = install(child, 'cubes-net', { nopy: { cubes: ['./cubes'] } });
|
|
|
|
const { packages, errors } = resolveCubePackages([
|
|
{ spec: 'cubes-net', from: path.join(tmpDir, 'nowhere') },
|
|
{ spec: 'cubes-net', from: child },
|
|
]);
|
|
|
|
expect(errors).toEqual([]);
|
|
expect(packages).toHaveLength(1);
|
|
expect(packages[0].root).toBe(dir);
|
|
});
|
|
});
|