stratifyjs 1.0.0

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +354 -0
  3. package/lib/adapters/config-file-loader.d.ts +8 -0
  4. package/lib/adapters/config-file-loader.js +55 -0
  5. package/lib/adapters/file-system-discovery.d.ts +22 -0
  6. package/lib/adapters/file-system-discovery.js +61 -0
  7. package/lib/api/api.d.ts +42 -0
  8. package/lib/api/api.js +75 -0
  9. package/lib/api/index.d.ts +15 -0
  10. package/lib/api/index.js +12 -0
  11. package/lib/cli/command-handler.d.ts +6 -0
  12. package/lib/cli/command-handler.js +50 -0
  13. package/lib/cli/index.d.ts +2 -0
  14. package/lib/cli/index.js +23 -0
  15. package/lib/cli/options.d.ts +18 -0
  16. package/lib/cli/options.js +21 -0
  17. package/lib/cli/output-helpers.d.ts +9 -0
  18. package/lib/cli/output-helpers.js +22 -0
  19. package/lib/core/config-defaults.d.ts +7 -0
  20. package/lib/core/config-defaults.js +22 -0
  21. package/lib/core/config-schema.d.ts +11 -0
  22. package/lib/core/config-schema.js +102 -0
  23. package/lib/core/errors.d.ts +44 -0
  24. package/lib/core/errors.js +25 -0
  25. package/lib/core/formatters/console-formatter.d.ts +3 -0
  26. package/lib/core/formatters/console-formatter.js +34 -0
  27. package/lib/core/formatters/json-formatter.d.ts +5 -0
  28. package/lib/core/formatters/json-formatter.js +11 -0
  29. package/lib/core/package-parser.d.ts +11 -0
  30. package/lib/core/package-parser.js +37 -0
  31. package/lib/core/report-builder.d.ts +18 -0
  32. package/lib/core/report-builder.js +17 -0
  33. package/lib/core/result.d.ts +33 -0
  34. package/lib/core/result.js +24 -0
  35. package/lib/core/rules.d.ts +14 -0
  36. package/lib/core/rules.js +19 -0
  37. package/lib/core/validation.d.ts +5 -0
  38. package/lib/core/validation.js +51 -0
  39. package/lib/types/types.d.ts +66 -0
  40. package/lib/types/types.js +1 -0
  41. package/package.json +67 -0
@@ -0,0 +1,50 @@
1
+ import { resolve } from 'path';
2
+ import { enforceLayersAsync, formatResults } from '../api/api.js';
3
+ import { formatLayerError } from '../api/index.js';
4
+ import { toLibraryOptions } from './options.js';
5
+ import { logInfo, logSuccess, logWarning, logError, logGray, logPlain } from './output-helpers.js';
6
+ /**
7
+ * Handle the main enforce command.
8
+ * Returns an exit code: 0 = success, 1 = failure.
9
+ */
10
+ export async function handleEnforceCommand(options) {
11
+ const workspaceRoot = resolve(options.root);
12
+ const configPath = resolve(workspaceRoot, options.config);
13
+ logInfo('🔍 Enforce Layers\n');
14
+ logGray(`Root: ${workspaceRoot}`);
15
+ logGray(`Config: ${configPath}\n`);
16
+ const result = await enforceLayersAsync(toLibraryOptions(options));
17
+ if (!result.success) {
18
+ logError(`❌ ${formatLayerError(result.error)}`);
19
+ return 1;
20
+ }
21
+ const { config, packages, violations, warnings } = result.value;
22
+ const effectiveMode = options.mode ?? config.enforcement.mode;
23
+ logSuccess(`✅ Loaded config with ${Object.keys(config.layers).length} layers`);
24
+ logGray(` Mode: ${effectiveMode}\n`);
25
+ logSuccess(`✅ Discovered ${packages.length} packages\n`);
26
+ if (warnings.length > 0) {
27
+ logWarning(`⚠️ ${warnings.length} warnings\n`);
28
+ for (const warning of warnings) {
29
+ logWarning(` • ${warning.path}: ${warning.message}`);
30
+ }
31
+ logPlain('');
32
+ }
33
+ // Get plain text report from formatter, add colors here
34
+ const plainReport = formatResults(result.value, options.format, effectiveMode);
35
+ if (options.format === 'json') {
36
+ logPlain(plainReport);
37
+ }
38
+ else {
39
+ if (violations.length > 0) {
40
+ logError(plainReport);
41
+ }
42
+ else {
43
+ logSuccess(plainReport);
44
+ }
45
+ }
46
+ if (effectiveMode === 'error' && violations.length > 0) {
47
+ return 1;
48
+ }
49
+ return 0;
50
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { createRequire } from 'module';
4
+ import { parseCliOptions } from './options.js';
5
+ import { handleEnforceCommand } from './command-handler.js';
6
+ const require = createRequire(import.meta.url);
7
+ const { version } = require('../../package.json');
8
+ program
9
+ .name('stratifyjs')
10
+ .description('Enforce package layering rules in monorepos')
11
+ .version(version)
12
+ .option('-c, --config <path>', 'Path to layer config file', 'stratify.config.json')
13
+ .option('-r, --root <path>', 'Workspace root directory', process.cwd())
14
+ .option('-m, --mode <mode>', 'Override enforcement mode (error|warn|off)')
15
+ .option('--format <type>', 'Output format (console|json)', 'console')
16
+ .parse(process.argv);
17
+ const options = parseCliOptions(program.opts());
18
+ handleEnforceCommand(options)
19
+ .then(code => process.exit(code))
20
+ .catch(error => {
21
+ console.error('Fatal error:', error);
22
+ process.exit(1);
23
+ });
@@ -0,0 +1,18 @@
1
+ import type { EnforceLayersOptions } from '../api/api.js';
2
+ /**
3
+ * Parsed CLI options.
4
+ */
5
+ export interface CliOptions {
6
+ config: string;
7
+ root: string;
8
+ mode?: 'error' | 'warn' | 'off';
9
+ format: 'console' | 'json';
10
+ }
11
+ /**
12
+ * Convert raw commander output into typed CliOptions.
13
+ */
14
+ export declare function parseCliOptions(raw: Record<string, unknown>): CliOptions;
15
+ /**
16
+ * Convert CLI options to library options.
17
+ */
18
+ export declare function toLibraryOptions(cli: CliOptions): EnforceLayersOptions;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Convert raw commander output into typed CliOptions.
3
+ */
4
+ export function parseCliOptions(raw) {
5
+ return {
6
+ config: raw.config ?? 'stratify.config.json',
7
+ root: raw.root ?? process.cwd(),
8
+ mode: raw.mode,
9
+ format: raw.format ?? 'console',
10
+ };
11
+ }
12
+ /**
13
+ * Convert CLI options to library options.
14
+ */
15
+ export function toLibraryOptions(cli) {
16
+ return {
17
+ workspaceRoot: cli.root,
18
+ configPath: cli.config,
19
+ mode: cli.mode,
20
+ };
21
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * CLI-specific output helpers with colored terminal formatting.
3
+ */
4
+ export declare function logInfo(message: string): void;
5
+ export declare function logSuccess(message: string): void;
6
+ export declare function logWarning(message: string): void;
7
+ export declare function logError(message: string): void;
8
+ export declare function logGray(message: string): void;
9
+ export declare function logPlain(message: string): void;
@@ -0,0 +1,22 @@
1
+ import pc from 'picocolors';
2
+ /**
3
+ * CLI-specific output helpers with colored terminal formatting.
4
+ */
5
+ export function logInfo(message) {
6
+ console.log(pc.blue(message));
7
+ }
8
+ export function logSuccess(message) {
9
+ console.log(pc.green(message));
10
+ }
11
+ export function logWarning(message) {
12
+ console.log(pc.yellow(message));
13
+ }
14
+ export function logError(message) {
15
+ console.error(pc.red(message));
16
+ }
17
+ export function logGray(message) {
18
+ console.log(pc.gray(message));
19
+ }
20
+ export function logPlain(message) {
21
+ console.log(message);
22
+ }
@@ -0,0 +1,7 @@
1
+ import type { LayerConfig, ResolvedConfig, EnforcementConfig, WorkspaceConfig } from '../types/types.js';
2
+ export declare const DEFAULT_ENFORCEMENT: EnforcementConfig;
3
+ export declare const DEFAULT_WORKSPACES: WorkspaceConfig;
4
+ /**
5
+ * Apply defaults to a validated LayerConfig, producing a fully resolved config.
6
+ */
7
+ export declare function applyDefaults(config: LayerConfig): ResolvedConfig;
@@ -0,0 +1,22 @@
1
+ export const DEFAULT_ENFORCEMENT = {
2
+ mode: 'warn',
3
+ };
4
+ export const DEFAULT_WORKSPACES = {
5
+ patterns: ['packages/**/*'],
6
+ };
7
+ /**
8
+ * Apply defaults to a validated LayerConfig, producing a fully resolved config.
9
+ */
10
+ export function applyDefaults(config) {
11
+ return {
12
+ layers: config.layers,
13
+ enforcement: {
14
+ ...DEFAULT_ENFORCEMENT,
15
+ ...config.enforcement,
16
+ },
17
+ workspaces: {
18
+ ...DEFAULT_WORKSPACES,
19
+ ...config.workspaces,
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,11 @@
1
+ import type { LayerConfig, LayerDefinition } from '../types/types.js';
2
+ import type { ConfigError } from './errors.js';
3
+ import type { Result } from './result.js';
4
+ /**
5
+ * Validate that a raw parsed object conforms to the LayerConfig schema.
6
+ */
7
+ export declare function validateConfigSchema(raw: unknown): Result<LayerConfig, ConfigError>;
8
+ /**
9
+ * Validate a single layer definition.
10
+ */
11
+ export declare function validateLayerDefinition(name: string, raw: unknown): Result<LayerDefinition, ConfigError>;
@@ -0,0 +1,102 @@
1
+ import { ok, err } from './result.js';
2
+ /**
3
+ * Validate that a raw parsed object conforms to the LayerConfig schema.
4
+ */
5
+ export function validateConfigSchema(raw) {
6
+ if (typeof raw !== 'object' || raw === null) {
7
+ return err({ type: 'config-validation-error', message: 'Config must be a JSON object' });
8
+ }
9
+ const obj = raw;
10
+ // Validate 'layers' field
11
+ if (typeof obj.layers !== 'object' || obj.layers === null) {
12
+ return err({
13
+ type: 'config-validation-error',
14
+ message: 'Config must have a "layers" object',
15
+ });
16
+ }
17
+ // Validate each layer definition
18
+ const layers = obj.layers;
19
+ const errors = [];
20
+ for (const [layerName, layerDef] of Object.entries(layers)) {
21
+ const result = validateLayerDefinition(layerName, layerDef);
22
+ if (!result.success) {
23
+ errors.push(result.error.message);
24
+ }
25
+ }
26
+ if (errors.length > 0) {
27
+ return err({
28
+ type: 'config-validation-error',
29
+ message: 'Invalid layer definitions',
30
+ details: errors,
31
+ });
32
+ }
33
+ // Validate optional 'enforcement' field
34
+ if (obj.enforcement !== undefined) {
35
+ if (typeof obj.enforcement !== 'object' || obj.enforcement === null) {
36
+ return err({
37
+ type: 'config-validation-error',
38
+ message: '"enforcement" field must be an object if defined',
39
+ });
40
+ }
41
+ const enforcement = obj.enforcement;
42
+ if (enforcement.mode !== undefined &&
43
+ (typeof enforcement.mode !== 'string' ||
44
+ !['error', 'warn', 'off'].includes(enforcement.mode))) {
45
+ return err({
46
+ type: 'config-validation-error',
47
+ message: `Invalid enforcement mode: "${enforcement.mode}". Must be "error", "warn", or "off"`,
48
+ });
49
+ }
50
+ }
51
+ // Validate optional 'workspaces'
52
+ if (obj.workspaces !== undefined) {
53
+ if (typeof obj.workspaces !== 'object' || obj.workspaces === null) {
54
+ return err({
55
+ type: 'config-validation-error',
56
+ message: '"workspaces" must be an object',
57
+ });
58
+ }
59
+ const workspaces = obj.workspaces;
60
+ if (workspaces.patterns !== undefined &&
61
+ (!Array.isArray(workspaces.patterns) ||
62
+ !workspaces.patterns.every((p) => typeof p === 'string'))) {
63
+ return err({
64
+ type: 'config-validation-error',
65
+ message: '"workspaces.patterns" must be an array of strings',
66
+ });
67
+ }
68
+ }
69
+ return ok({
70
+ layers: obj.layers,
71
+ enforcement: obj.enforcement,
72
+ workspaces: obj.workspaces,
73
+ });
74
+ }
75
+ /**
76
+ * Validate a single layer definition.
77
+ */
78
+ export function validateLayerDefinition(name, raw) {
79
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
80
+ return err({
81
+ type: 'config-validation-error',
82
+ message: `Layer "${name}" must be an object`,
83
+ });
84
+ }
85
+ const def = raw;
86
+ if (!Array.isArray(def.allowedDependencies)) {
87
+ return err({
88
+ type: 'config-validation-error',
89
+ message: `Layer "${name}" must have an "allowedDependencies" array`,
90
+ });
91
+ }
92
+ if (!def.allowedDependencies.every((d) => typeof d === 'string')) {
93
+ return err({
94
+ type: 'config-validation-error',
95
+ message: `Layer "${name}" allowedDependencies must contain only strings`,
96
+ });
97
+ }
98
+ return ok({
99
+ description: typeof def.description === 'string' ? def.description : undefined,
100
+ allowedDependencies: def.allowedDependencies,
101
+ });
102
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Union of all possible errors in the enforce-layers system.
3
+ */
4
+ export type LayerError = ConfigError | DiscoveryError;
5
+ /**
6
+ * Errors that can occur during configuration loading and validation.
7
+ */
8
+ export type ConfigError = {
9
+ type: 'config-not-found';
10
+ message: string;
11
+ path: string;
12
+ } | {
13
+ type: 'config-read-error';
14
+ message: string;
15
+ path: string;
16
+ cause?: unknown;
17
+ } | {
18
+ type: 'config-parse-error';
19
+ message: string;
20
+ path: string;
21
+ cause?: unknown;
22
+ } | {
23
+ type: 'config-validation-error';
24
+ message: string;
25
+ details?: string[];
26
+ };
27
+ /**
28
+ * Errors that can occur during package discovery.
29
+ */
30
+ export type DiscoveryError = {
31
+ type: 'glob-failed';
32
+ message: string;
33
+ pattern: string;
34
+ cause?: unknown;
35
+ } | {
36
+ type: 'package-parse-error';
37
+ message: string;
38
+ path: string;
39
+ cause?: unknown;
40
+ };
41
+ /**
42
+ * Format any LayerError into a human-readable string.
43
+ */
44
+ export declare function formatLayerError(error: LayerError): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Format any LayerError into a human-readable string.
3
+ */
4
+ export function formatLayerError(error) {
5
+ switch (error.type) {
6
+ case 'config-not-found':
7
+ return `Config file not found: ${error.path}`;
8
+ case 'config-read-error':
9
+ return `Failed to read config file (${error.path}): ${error.message}`;
10
+ case 'config-parse-error':
11
+ return `Invalid JSON in config file (${error.path}): ${error.message}`;
12
+ case 'config-validation-error':
13
+ return error.details
14
+ ? `Config validation failed:\n${error.details.map(d => ` - ${d}`).join('\n')}`
15
+ : `Config validation failed: ${error.message}`;
16
+ case 'glob-failed':
17
+ return `Glob pattern failed (${error.pattern}): ${error.message}`;
18
+ case 'package-parse-error':
19
+ return `Failed to parse package at ${error.path}: ${error.message}`;
20
+ default: {
21
+ const _exhaustive = error;
22
+ return `Unknown error: ${_exhaustive.message}`;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { ValidationReport } from '../report-builder.js';
2
+ import type { EnforcementConfig } from '../../types/types.js';
3
+ export declare function formatConsole(report: ValidationReport, mode: EnforcementConfig['mode']): string;
@@ -0,0 +1,34 @@
1
+ const TYPE_LABELS = {
2
+ 'missing-layer': '🏷️ Missing Layer',
3
+ 'unknown-layer': '❓ Unknown Layer',
4
+ 'invalid-dependency': '🚫 Invalid Dependency',
5
+ };
6
+ export function formatConsole(report, mode) {
7
+ const lines = [];
8
+ const durationStr = formatDuration(report.duration);
9
+ if (report.violations.length === 0) {
10
+ lines.push('✅ All packages comply with layer rules!');
11
+ lines.push(`\n⏱️ Completed in ${durationStr}`);
12
+ return lines.join('\n');
13
+ }
14
+ lines.push(`❌ Found ${report.violationCount} layer violations:\n`);
15
+ for (const [type, violations] of Object.entries(report.violationsByType)) {
16
+ if (!violations || violations.length === 0) {
17
+ continue;
18
+ }
19
+ const label = TYPE_LABELS[type] ?? type;
20
+ lines.push(` ${label} (${violations.length}):`);
21
+ for (const v of violations) {
22
+ lines.push(` • ${v.message}`);
23
+ }
24
+ lines.push('');
25
+ }
26
+ lines.push(`⏱️ Completed in ${durationStr}`);
27
+ if (mode === 'warn') {
28
+ lines.push('\n⚠️ Enforcement mode: warn - not failing build');
29
+ }
30
+ return lines.join('\n');
31
+ }
32
+ function formatDuration(ms) {
33
+ return ms < 1000 ? `${ms.toFixed(0)}ms` : `${(ms / 1000).toFixed(2)}s`;
34
+ }
@@ -0,0 +1,5 @@
1
+ import type { ValidationReport } from '../report-builder.js';
2
+ /**
3
+ * Format a validation report as a JSON string.
4
+ */
5
+ export declare function formatJson(report: ValidationReport): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Format a validation report as a JSON string.
3
+ */
4
+ export function formatJson(report) {
5
+ return JSON.stringify({
6
+ violations: report.violations,
7
+ totalPackages: report.totalPackages,
8
+ violationCount: report.violationCount,
9
+ duration: report.duration,
10
+ }, null, 2);
11
+ }
@@ -0,0 +1,11 @@
1
+ import type { Package } from '../types/types.js';
2
+ import type { DiscoveryError } from './errors.js';
3
+ import type { Result } from './result.js';
4
+ /**
5
+ * Parse a raw package.json object into a Package.
6
+ */
7
+ export declare function parsePackageJson(content: unknown, relativePath: string): Result<Package, DiscoveryError>;
8
+ /**
9
+ * Extract workspace: protocol dependencies from deps, devDeps, and peerDeps.
10
+ */
11
+ export declare function extractWorkspaceDependencies(dependencies?: Record<string, string>, devDependencies?: Record<string, string>, peerDependencies?: Record<string, string>): string[];
@@ -0,0 +1,37 @@
1
+ import { ok, err } from './result.js';
2
+ /**
3
+ * Parse a raw package.json object into a Package.
4
+ */
5
+ export function parsePackageJson(content, relativePath) {
6
+ if (typeof content !== 'object' || content === null) {
7
+ return err({
8
+ type: 'package-parse-error',
9
+ message: `Invalid package.json at "${relativePath}": must be a JSON object`,
10
+ path: relativePath,
11
+ });
12
+ }
13
+ const pkg = content;
14
+ // Validate required fields
15
+ if (typeof pkg.name !== 'string' || pkg.name.trim() === '') {
16
+ return err({
17
+ type: 'package-parse-error',
18
+ message: `Invalid package.json at "${relativePath}": missing or invalid "name" field`,
19
+ path: relativePath,
20
+ });
21
+ }
22
+ return ok({
23
+ name: pkg.name,
24
+ layer: typeof pkg.layer === 'string' ? pkg.layer : undefined,
25
+ dependencies: extractWorkspaceDependencies(pkg.dependencies, pkg.devDependencies, pkg.peerDependencies),
26
+ path: relativePath,
27
+ });
28
+ }
29
+ /**
30
+ * Extract workspace: protocol dependencies from deps, devDeps, and peerDeps.
31
+ */
32
+ export function extractWorkspaceDependencies(dependencies, devDependencies, peerDependencies) {
33
+ const allDeps = { ...dependencies, ...devDependencies, ...peerDependencies };
34
+ return Object.entries(allDeps)
35
+ .filter(([_, version]) => typeof version === 'string' && version.startsWith('workspace:'))
36
+ .map(([name, _]) => name);
37
+ }
@@ -0,0 +1,18 @@
1
+ import type { Violation, ViolationType } from '../types/types.js';
2
+ /**
3
+ * Structured report produced after validation.
4
+ */
5
+ export interface ValidationReport {
6
+ violations: Violation[];
7
+ totalPackages: number;
8
+ violationsByType: Partial<Record<ViolationType, Violation[]>>;
9
+ violationCount: number;
10
+ duration: number;
11
+ }
12
+ /**
13
+ * Build a structured report from violations and metadata.
14
+ */
15
+ export declare function buildReport(violations: Violation[], metadata: {
16
+ totalPackages: number;
17
+ duration: number;
18
+ }): ValidationReport;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Build a structured report from violations and metadata.
3
+ */
4
+ export function buildReport(violations, metadata) {
5
+ const violationsByType = {};
6
+ // Group violations by type for easier reporting
7
+ for (const violation of violations) {
8
+ (violationsByType[violation.type] ??= []).push(violation);
9
+ }
10
+ return {
11
+ violations,
12
+ totalPackages: metadata.totalPackages,
13
+ violationsByType,
14
+ violationCount: violations.length,
15
+ duration: metadata.duration,
16
+ };
17
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A discriminated union representing either a successful value or an error.
3
+ * Used throughout the library to make error handling explicit and type-safe.
4
+ */
5
+ export type Result<T, E> = {
6
+ readonly success: true;
7
+ readonly value: T;
8
+ } | {
9
+ readonly success: false;
10
+ readonly error: E;
11
+ };
12
+ /**
13
+ * Create a successful Result containing the given value.
14
+ */
15
+ export declare function ok<T>(value: T): Result<T, never>;
16
+ /**
17
+ * Create a failed Result containing the given error.
18
+ */
19
+ export declare function err<E>(error: E): Result<never, E>;
20
+ /**
21
+ * Type guard for successful Results.
22
+ */
23
+ export declare function isOk<T, E>(result: Result<T, E>): result is {
24
+ success: true;
25
+ value: T;
26
+ };
27
+ /**
28
+ * Type guard for failed Results.
29
+ */
30
+ export declare function isErr<T, E>(result: Result<T, E>): result is {
31
+ success: false;
32
+ error: E;
33
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Create a successful Result containing the given value.
3
+ */
4
+ export function ok(value) {
5
+ return { success: true, value };
6
+ }
7
+ /**
8
+ * Create a failed Result containing the given error.
9
+ */
10
+ export function err(error) {
11
+ return { success: false, error };
12
+ }
13
+ /**
14
+ * Type guard for successful Results.
15
+ */
16
+ export function isOk(result) {
17
+ return result.success;
18
+ }
19
+ /**
20
+ * Type guard for failed Results.
21
+ */
22
+ export function isErr(result) {
23
+ return !result.success;
24
+ }
@@ -0,0 +1,14 @@
1
+ import type { Package, LayerMap } from '../types/types.js';
2
+ /**
3
+ * Check whether a package has the required "layer" field.
4
+ */
5
+ export declare function hasRequiredLayer(pkg: Package): boolean;
6
+ /**
7
+ * Check whether a layer name is defined in the config.
8
+ */
9
+ export declare function isKnownLayer(layerName: string, layers: LayerMap): boolean;
10
+ /**
11
+ * Check whether a dependency from one layer to another is permitted.
12
+ * Wildcard '*' in allowedDependencies permits any target layer.
13
+ */
14
+ export declare function isDependencyAllowed(fromLayer: string, toLayer: string, allowedDependencies: string[]): boolean;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Check whether a package has the required "layer" field.
3
+ */
4
+ export function hasRequiredLayer(pkg) {
5
+ return pkg.layer !== undefined && pkg.layer !== '';
6
+ }
7
+ /**
8
+ * Check whether a layer name is defined in the config.
9
+ */
10
+ export function isKnownLayer(layerName, layers) {
11
+ return layerName in layers;
12
+ }
13
+ /**
14
+ * Check whether a dependency from one layer to another is permitted.
15
+ * Wildcard '*' in allowedDependencies permits any target layer.
16
+ */
17
+ export function isDependencyAllowed(fromLayer, toLayer, allowedDependencies) {
18
+ return allowedDependencies.includes('*') || allowedDependencies.includes(toLayer);
19
+ }
@@ -0,0 +1,5 @@
1
+ import type { Package, ResolvedConfig, Violation } from '../types/types.js';
2
+ /**
3
+ * Validate all packages against layer configuration.
4
+ */
5
+ export declare function validatePackages(packages: Package[], config: ResolvedConfig): Violation[];