or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

edge-cases.mddocs/examples/

Edge Cases

Advanced scenarios, special cases, and error recovery patterns.

Handling Missing Fields

Packages Without Resolved Field

Packages installed from local filesystem don't have a resolved field. URL-based validators automatically skip these:

// This package will be automatically skipped by URL validators
{
  'local-package@1.0.0': {
    version: '1.0.0'
    // No resolved field - automatically skipped
  }
}

Behavior: All URL-based validators (ValidateHost, ValidateHttps, ValidateScheme, ValidateUrl, ValidatePackageNames) automatically skip packages without a resolved field.

Packages Without Integrity Field

Git dependencies and older lockfiles may not have integrity hashes:

// This package will be automatically skipped by ValidateIntegrity
{
  'git-package@1.0.0': {
    version: '1.0.0',
    resolved: 'git+https://github.com/user/repo.git'
    // No integrity field - automatically skipped
  }
}

Behavior: ValidateIntegrity automatically skips packages without an integrity field.

Lockfile Format Variations

Handling Multiple npm Versions

The parser automatically detects npm v1/v2 vs v3 format:

const parser = new ParseLockfile({
  lockfilePath: './package-lock.json',
  lockfileType: 'npm'
});
// Automatically detects v1/v2 vs v3 format
const lockfile = parser.parseSync();

Handling Yarn Classic vs Berry

The parser automatically detects and normalizes Yarn formats:

const parser = new ParseLockfile({
  lockfilePath: './yarn.lock',
  lockfileType: 'yarn'
});
// Automatically detects classic vs Berry format
// Berry format is normalized to match classic structure
const lockfile = parser.parseSync();

Non-Standard Filenames

For non-standard filenames, always specify lockfileType explicitly:

// npm-shrinkwrap.json is NOT auto-detected by filename
const parser = new ParseLockfile({
  lockfilePath: './npm-shrinkwrap.json',
  lockfileType: 'npm'  // Required!
});

// Custom lockfile names
const parser = new ParseLockfile({
  lockfilePath: './custom.lock',
  lockfileType: 'yarn'  // Required!
});

Error Recovery Patterns

Safe Parsing with Fallback

function safeParseLockfile(path, type) {
  try {
    const parser = new ParseLockfile({
      lockfilePath: path,
      lockfileType: type
    });
    return parser.parseSync();
  } catch (error) {
    if (error.name === 'ParsingError') {
      // Handle parsing-specific errors
      if (error.message.includes('NO_PARSER_FOR_PATH')) {
        // Try with explicit type
        return safeParseLockfile(path, 'npm');
      }
      if (error.message.includes('READ_FAILED')) {
        // Try reading as text
        const fs = require('fs');
        const content = fs.readFileSync(path, 'utf-8');
        const parser = new ParseLockfile({
          lockfileText: content,
          lockfileType: type
        });
        return parser.parseSync();
      }
    }
    throw error;
  }
}

Handling Empty Hostnames

By default, empty hostnames are rejected. Allow them if needed:

const validator = new ValidateHost({ packages: lockfile.object });

// Default: empty hostnames are rejected
const result1 = validator.validate(['npm']);
// Packages with empty hostnames will fail

// Allow empty hostnames
const result2 = validator.validate(['npm'], { emptyHostname: true });
// Packages with empty hostnames will pass

URL Variations

Registry Shortcuts vs Full URLs

All these are equivalent when using shortcuts:

const validator = new ValidateHost({ packages: lockfile.object });

// All equivalent
validator.validate(['npm']);
validator.validate(['registry.npmjs.org']);
validator.validate(['https://registry.npmjs.org']);

// Full URLs have hostname extracted automatically
validator.validate(['https://registry.npmjs.org/path/to/resource']);
// Only 'registry.npmjs.org' is used for comparison

URL Scheme Format

Schemes must include the trailing colon:

const validator = new ValidateScheme({ packages: lockfile.object });

// Correct format (with colon)
validator.validate(['https:', 'http:', 'git:']);

// Incorrect format (without colon) - will not match
validator.validate(['https', 'http', 'git']);  // Will fail validation

Package Name Aliases

Handling Legitimate Aliases

Some packages legitimately use different names in lockfile vs registry:

const validator = new ValidatePackageNames({ packages: lockfile.object });

// Allow specific package name aliases
// Format: 'declared-name:actual-name'
const result = validator.validate([
  'my-lodash:lodash',  // package.json has "my-lodash": "npm:lodash@4.17.21"
  'my-react:react',
  'company-utils:@company/utils'
]);

Integrity Hash Exclusions

Excluding Legacy Packages

Exclusions match by package name prefix:

const validator = new ValidateIntegrity({ packages: lockfile.object });

// Exclusion matches by prefix
integrityExclude: ['lodash']  // Matches 'lodash@4.17.21', 'lodash@4.18.0', etc.
integrityExclude: ['@babel/core']  // Matches '@babel/core@7.20.0', '@babel/core@7.21.0', etc.

// Exclude multiple packages
const result = validator.validate({
  integrityExclude: [
    'legacy-package',
    'old-dependency',
    '@company/legacy-lib',
    '@types/old-types'
  ]
});

Private Registry Handling

ValidateHost with Private Registries

const validator = new ValidateHost({ packages: lockfile.object });

// Allow multiple registries
const result = validator.validate([
  'npm',  // Public npm
  'registry.mycompany.com',  // Private corporate registry
  'registry.verdaccio.org'  // Verdaccio
]);

ValidatePackageNames with Private Registries

ValidatePackageNames only validates packages from official public registries (npm, yarn, verdaccio). Packages from private registries are automatically skipped:

// These will be validated
resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'
resolved: 'https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz'

// These will be skipped (private registries)
resolved: 'https://registry.mycompany.com/lodash/-/lodash-4.17.21.tgz'
resolved: 'https://nexus.example.org/lodash/-/lodash-4.17.21.tgz'

Error Handling Edge Cases

ParsingError Types

Handle different parsing error types:

try {
  const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
  const lockfile = parser.parseSync();
} catch (error) {
  if (error.name === 'ParsingError') {
    switch (true) {
      case error.message.includes('NO_LOCKFILE'):
        console.error('No lockfile path or content provided');
        break;
      case error.message.includes('NO_PARSER_FOR_PATH'):
        console.error('Cannot determine lockfile type. Specify lockfileType explicitly.');
        break;
      case error.message.includes('READ_FAILED'):
        console.error('Cannot read lockfile from filesystem');
        break;
      case error.message.includes('PARSE_NPMLOCKFILE_FAILED'):
        console.error('npm lockfile parsing failed - invalid JSON or format');
        break;
      case error.message.includes('PARSE_YARNLOCKFILE_FAILED'):
        console.error('yarn lockfile parsing failed - invalid SYML or format');
        break;
      default:
        console.error('Parsing error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Validation Error Aggregation

Collect errors from multiple validators:

const {
  ParseLockfile,
  ValidateHost,
  ValidateHttps,
  ValidateIntegrity
} = require('lockfile-lint-api');

function collectAllErrors(lockfilePath) {
  const parser = new ParseLockfile({ lockfilePath });
  const lockfile = parser.parseSync();

  const allErrors = [];

  // HTTPS validation
  const httpsValidator = new ValidateHttps({ packages: lockfile.object });
  const httpsResult = httpsValidator.validate();
  if (httpsResult.type === 'error') {
    allErrors.push(...httpsResult.errors.map(e => ({ type: 'https', ...e })));
  }

  // Host validation
  const hostValidator = new ValidateHost({ packages: lockfile.object });
  const hostResult = hostValidator.validate(['npm']);
  if (hostResult.type === 'error') {
    allErrors.push(...hostResult.errors.map(e => ({ type: 'host', ...e })));
  }

  // Integrity validation
  const integrityValidator = new ValidateIntegrity({ packages: lockfile.object });
  const integrityResult = integrityValidator.validate();
  if (integrityResult.type === 'error') {
    allErrors.push(...integrityResult.errors.map(e => ({ type: 'integrity', ...e })));
  }

  return allErrors;
}

const errors = collectAllErrors('./package-lock.json');
if (errors.length > 0) {
  console.error(`Found ${errors.length} validation errors:`);
  errors.forEach(error => {
    console.error(`[${error.type}] ${error.package}: ${error.message}`);
  });
}

ValidateSingle vs Validate

When to Use validateSingle

Use validateSingle for targeted checks on specific packages:

const validator = new ValidateHost({ packages: lockfile.object });

// Check specific critical packages
const criticalPackages = ['lodash@4.17.21', 'express@4.18.2', 'react@18.2.0'];

criticalPackages.forEach(packageName => {
  if (!validator.validateSingle(packageName, ['npm'])) {
    console.error(`CRITICAL: ${packageName} is not from allowed hosts`);
    process.exit(1);
  }
});

Note: validateSingle may throw errors if URL parsing fails, unlike validate() which catches errors silently.

URL Whitelisting Edge Cases

Exact URL Matching

URL whitelisting requires exact matches - no pattern matching:

const validator = new ValidateUrl({ packages: lockfile.object });

// Exact match required
const allowedUrls = [
  'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'
];

// This will PASS
// resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'

// This will FAIL (different version)
// resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.22.tgz'

// This will FAIL (different path)
// resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz?cache=bust'

Case Sensitivity

URL matching is case-sensitive:

// These are different URLs
'https://registry.npmjs.org/package/-/package-1.0.0.tgz'
'https://registry.npmjs.org/Package/-/Package-1.0.0.tgz'  // Different!