Compare commits
5 Commits
wmill-scri
...
feat/folde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbeed2ccef | ||
|
|
6869e4c3e2 | ||
|
|
fd2cb93cac | ||
|
|
23cf2834b3 | ||
|
|
77d59d7371 |
@@ -53,6 +53,7 @@ import {
|
|||||||
getSpecificItemsForCurrentBranch,
|
getSpecificItemsForCurrentBranch,
|
||||||
isBranchSpecificFile,
|
isBranchSpecificFile,
|
||||||
isCurrentBranchFile,
|
isCurrentBranchFile,
|
||||||
|
isItemTypeConfigured,
|
||||||
isSpecificItem,
|
isSpecificItem,
|
||||||
SpecificItemsConfig,
|
SpecificItemsConfig,
|
||||||
} from "../../core/specific_items.ts";
|
} from "../../core/specific_items.ts";
|
||||||
@@ -1350,10 +1351,12 @@ export async function elementsToMap(
|
|||||||
// If getTypeStrFromPath can't determine the type, continue processing the file
|
// If getTypeStrFromPath can't determine the type, continue processing the file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle branch-specific files - skip files for other branches
|
// Handle branch-specific files - ALWAYS skip files that don't match current branch
|
||||||
if (specificItems && isBranchSpecificFile(path)) {
|
// This applies regardless of specificItems config - branch-specific files by naming
|
||||||
|
// convention should only be processed when on the matching branch
|
||||||
|
if (isBranchSpecificFile(path)) {
|
||||||
if (!isCurrentBranchFile(path, branchOverride)) {
|
if (!isCurrentBranchFile(path, branchOverride)) {
|
||||||
// Skip branch-specific files for other branches
|
// Skip branch-specific files for other branches (or when not on any branch)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1387,32 +1390,36 @@ export async function elementsToMap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle branch-specific path mapping after all filtering
|
// Handle branch-specific path mapping after all filtering
|
||||||
if (specificItems) {
|
if (isCurrentBranchFile(path, branchOverride)) {
|
||||||
if (isCurrentBranchFile(path, branchOverride)) {
|
// This is a branch-specific file for current branch - map to base path
|
||||||
// This is a branch-specific file for current branch
|
const currentBranch = branchOverride || getCurrentGitBranch()!;
|
||||||
// Safe to compute branch here since isCurrentBranchFile already validated it exists
|
const basePath = fromBranchSpecificPath(path, currentBranch);
|
||||||
const currentBranch = branchOverride || getCurrentGitBranch()!;
|
|
||||||
const basePath = fromBranchSpecificPath(path, currentBranch);
|
// Only apply specificItems filtering if the item TYPE is configured
|
||||||
if (isSpecificItem(basePath, specificItems)) {
|
// If the type is configured but doesn't match the pattern, skip it
|
||||||
// Map to base path for push operations
|
// If the type is NOT configured, process the file normally
|
||||||
map[basePath] = content;
|
if (isItemTypeConfigured(basePath, specificItems) && !isSpecificItem(basePath, specificItems)) {
|
||||||
processedBasePaths.add(basePath);
|
// Branch-specific file doesn't match the configured pattern, skip it
|
||||||
} else {
|
continue;
|
||||||
// Branch-specific file doesn't match pattern, skip it
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (!isBranchSpecificFile(path)) {
|
|
||||||
// This is a regular base file, check if we should skip it
|
|
||||||
if (processedBasePaths.has(path)) {
|
|
||||||
// Skip base file, we already processed branch-specific version
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
map[path] = content;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No specific items configuration, use regular path
|
// Map to base path for push operations
|
||||||
map[entry.path] = content;
|
map[basePath] = content;
|
||||||
|
processedBasePaths.add(basePath);
|
||||||
|
} else if (!isBranchSpecificFile(path)) {
|
||||||
|
// This is a regular base file
|
||||||
|
if (processedBasePaths.has(path)) {
|
||||||
|
// Skip base file, we already processed branch-specific version
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Only skip base files if this item type IS configured as branch-specific
|
||||||
|
// AND it matches the pattern (meaning we expect a branch-specific version)
|
||||||
|
if (isSpecificItem(path, specificItems)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
map[path] = content;
|
||||||
}
|
}
|
||||||
|
// Note: branch-specific files for other branches are already filtered out above
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -1866,9 +1873,9 @@ export async function pull(
|
|||||||
const originalCliOpts = { ...opts };
|
const originalCliOpts = { ...opts };
|
||||||
opts = await mergeConfigWithConfigFile(opts);
|
opts = await mergeConfigWithConfigFile(opts);
|
||||||
|
|
||||||
// Validate branch configuration early
|
// Validate branch configuration early (skipped when --branch is used)
|
||||||
try {
|
try {
|
||||||
await validateBranchConfiguration(opts);
|
await validateBranchConfiguration(opts, opts.branch);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message.includes("overrides")) {
|
if (error instanceof Error && error.message.includes("overrides")) {
|
||||||
log.error(error.message);
|
log.error(error.message);
|
||||||
@@ -2351,9 +2358,9 @@ export async function push(
|
|||||||
// Load configuration from wmill.yaml and merge with CLI options
|
// Load configuration from wmill.yaml and merge with CLI options
|
||||||
opts = await mergeConfigWithConfigFile(opts);
|
opts = await mergeConfigWithConfigFile(opts);
|
||||||
|
|
||||||
// Validate branch configuration early
|
// Validate branch configuration early (skipped when --branch is used)
|
||||||
try {
|
try {
|
||||||
await validateBranchConfiguration(opts);
|
await validateBranchConfiguration(opts, opts.branch);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message.includes("overrides")) {
|
if (error instanceof Error && error.message.includes("overrides")) {
|
||||||
log.error(error.message);
|
log.error(error.message);
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ export interface SyncOptions {
|
|||||||
variables?: string[];
|
variables?: string[];
|
||||||
resources?: string[];
|
resources?: string[];
|
||||||
triggers?: string[];
|
triggers?: string[];
|
||||||
|
folders?: string[];
|
||||||
|
settings?: boolean;
|
||||||
};
|
};
|
||||||
} & {
|
} & {
|
||||||
[branchName: string]: SyncOptions & {
|
[branchName: string]: SyncOptions & {
|
||||||
@@ -64,6 +66,8 @@ export interface SyncOptions {
|
|||||||
variables?: string[];
|
variables?: string[];
|
||||||
resources?: string[];
|
resources?: string[];
|
||||||
triggers?: string[];
|
triggers?: string[];
|
||||||
|
folders?: string[];
|
||||||
|
settings?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -73,6 +77,8 @@ export interface SyncOptions {
|
|||||||
variables?: string[];
|
variables?: string[];
|
||||||
resources?: string[];
|
resources?: string[];
|
||||||
triggers?: string[];
|
triggers?: string[];
|
||||||
|
folders?: string[];
|
||||||
|
settings?: boolean;
|
||||||
};
|
};
|
||||||
} & {
|
} & {
|
||||||
[branchName: string]: SyncOptions & {
|
[branchName: string]: SyncOptions & {
|
||||||
@@ -84,6 +90,8 @@ export interface SyncOptions {
|
|||||||
variables?: string[];
|
variables?: string[];
|
||||||
resources?: string[];
|
resources?: string[];
|
||||||
triggers?: string[];
|
triggers?: string[];
|
||||||
|
folders?: string[];
|
||||||
|
settings?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -370,9 +378,11 @@ export async function mergeConfigWithConfigFile<T>(
|
|||||||
|
|
||||||
// Validate branch configuration early in the process
|
// Validate branch configuration early in the process
|
||||||
export async function validateBranchConfiguration(
|
export async function validateBranchConfiguration(
|
||||||
opts: Pick<SyncOptions, "skipBranchValidation" | "yes">
|
opts: Pick<SyncOptions, "skipBranchValidation" | "yes">,
|
||||||
|
branchOverride?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (opts.skipBranchValidation || !isGitRepository()) {
|
// When branch override is provided, skip validation - user is explicitly specifying the branch
|
||||||
|
if (opts.skipBranchValidation || branchOverride || !isGitRepository()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export interface SpecificItemsConfig {
|
|||||||
variables?: string[];
|
variables?: string[];
|
||||||
resources?: string[];
|
resources?: string[];
|
||||||
triggers?: string[];
|
triggers?: string[];
|
||||||
|
folders?: string[];
|
||||||
|
settings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define all branch-specific file types (computed lazily)
|
// Define all branch-specific file types (computed lazily)
|
||||||
@@ -98,6 +100,12 @@ export function getSpecificItemsForCurrentBranch(config: SyncOptions, branchOver
|
|||||||
if (commonItems?.triggers) {
|
if (commonItems?.triggers) {
|
||||||
merged.triggers = [...commonItems.triggers];
|
merged.triggers = [...commonItems.triggers];
|
||||||
}
|
}
|
||||||
|
if (commonItems?.folders) {
|
||||||
|
merged.folders = [...commonItems.folders];
|
||||||
|
}
|
||||||
|
if (commonItems?.settings !== undefined) {
|
||||||
|
merged.settings = commonItems.settings;
|
||||||
|
}
|
||||||
|
|
||||||
// Add branch-specific items (extending common items)
|
// Add branch-specific items (extending common items)
|
||||||
if (branchItems?.variables) {
|
if (branchItems?.variables) {
|
||||||
@@ -109,6 +117,13 @@ export function getSpecificItemsForCurrentBranch(config: SyncOptions, branchOver
|
|||||||
if (branchItems?.triggers) {
|
if (branchItems?.triggers) {
|
||||||
merged.triggers = [...(merged.triggers || []), ...branchItems.triggers];
|
merged.triggers = [...(merged.triggers || []), ...branchItems.triggers];
|
||||||
}
|
}
|
||||||
|
if (branchItems?.folders) {
|
||||||
|
merged.folders = [...(merged.folders || []), ...branchItems.folders];
|
||||||
|
}
|
||||||
|
// For settings (boolean), branch-specific overrides common
|
||||||
|
if (branchItems?.settings !== undefined) {
|
||||||
|
merged.settings = branchItems.settings;
|
||||||
|
}
|
||||||
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
@@ -120,6 +135,42 @@ function matchesPatterns(path: string, patterns: string[]): boolean {
|
|||||||
return patterns.some(pattern => minimatch(path, pattern));
|
return patterns.some(pattern => minimatch(path, pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the item type for a given path is configured in specificItems
|
||||||
|
* This is different from isSpecificItem which checks if it MATCHES the pattern
|
||||||
|
*/
|
||||||
|
export function isItemTypeConfigured(path: string, specificItems: SpecificItemsConfig | undefined): boolean {
|
||||||
|
if (!specificItems) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.endsWith('.variable.yaml')) {
|
||||||
|
return specificItems.variables !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.endsWith('.resource.yaml')) {
|
||||||
|
return specificItems.resources !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTriggerFile(path)) {
|
||||||
|
return specificItems.triggers !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.endsWith('/folder.meta.yaml')) {
|
||||||
|
return specificItems.folders !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === 'settings.yaml') {
|
||||||
|
return specificItems.settings !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFileResource(path)) {
|
||||||
|
return specificItems.resources !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a file path should be treated as branch-specific
|
* Check if a file path should be treated as branch-specific
|
||||||
*/
|
*/
|
||||||
@@ -142,6 +193,21 @@ export function isSpecificItem(path: string, specificItems: SpecificItemsConfig
|
|||||||
return specificItems.triggers ? matchesPatterns(path, specificItems.triggers) : false;
|
return specificItems.triggers ? matchesPatterns(path, specificItems.triggers) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for folder meta files
|
||||||
|
if (path.endsWith('/folder.meta.yaml')) {
|
||||||
|
if (specificItems.folders) {
|
||||||
|
// Match against the folder path (without /folder.meta.yaml)
|
||||||
|
const folderPath = path.slice(0, -'/folder.meta.yaml'.length);
|
||||||
|
return matchesPatterns(folderPath, specificItems.folders);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for settings.yaml (root-level file)
|
||||||
|
if (path === 'settings.yaml') {
|
||||||
|
return specificItems.settings === true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for resource files using the standard detection function
|
// Check for resource files using the standard detection function
|
||||||
if (isFileResource(path)) {
|
if (isFileResource(path)) {
|
||||||
// Extract the base path without the file extension to match against patterns
|
// Extract the base path without the file extension to match against patterns
|
||||||
@@ -159,6 +225,25 @@ export function isSpecificItem(path: string, specificItems: SpecificItemsConfig
|
|||||||
* Convert a base path to a branch-specific path
|
* Convert a base path to a branch-specific path
|
||||||
*/
|
*/
|
||||||
export function toBranchSpecificPath(basePath: string, branchName: string): string {
|
export function toBranchSpecificPath(basePath: string, branchName: string): string {
|
||||||
|
// Sanitize branch name to be filesystem-safe
|
||||||
|
const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_');
|
||||||
|
|
||||||
|
// Warn about potential collisions if sanitization occurred
|
||||||
|
if (sanitizedBranchName !== branchName) {
|
||||||
|
console.warn(`Warning: Branch name "${branchName}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .) and was sanitized to "${sanitizedBranchName}". This may cause collisions with other similarly named branches.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for folder meta file pattern: folder.meta.yaml -> folder.branchName.meta.yaml
|
||||||
|
if (basePath.endsWith('/folder.meta.yaml')) {
|
||||||
|
const pathWithoutMeta = basePath.substring(0, basePath.length - '/folder.meta.yaml'.length);
|
||||||
|
return `${pathWithoutMeta}/folder.${sanitizedBranchName}.meta.yaml`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for settings.yaml: settings.yaml -> settings.branchName.yaml
|
||||||
|
if (basePath === 'settings.yaml') {
|
||||||
|
return `settings.${sanitizedBranchName}.yaml`;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for resource file pattern (e.g., .resource.file.ini)
|
// Check for resource file pattern (e.g., .resource.file.ini)
|
||||||
const resourceFileMatch = basePath.match(/^(.+?)(\.resource\.file\..+)$/);
|
const resourceFileMatch = basePath.match(/^(.+?)(\.resource\.file\..+)$/);
|
||||||
|
|
||||||
@@ -178,14 +263,6 @@ export function toBranchSpecificPath(basePath: string, branchName: string): stri
|
|||||||
pathWithoutExtension = basePath.substring(0, basePath.length - extension.length);
|
pathWithoutExtension = basePath.substring(0, basePath.length - extension.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize branch name to be filesystem-safe
|
|
||||||
const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_');
|
|
||||||
|
|
||||||
// Warn about potential collisions if sanitization occurred
|
|
||||||
if (sanitizedBranchName !== branchName) {
|
|
||||||
console.warn(`Warning: Branch name "${branchName}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .) and was sanitized to "${sanitizedBranchName}". This may cause collisions with other similarly named branches.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${pathWithoutExtension}.${sanitizedBranchName}${extension}`;
|
return `${pathWithoutExtension}.${sanitizedBranchName}${extension}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +274,19 @@ export function fromBranchSpecificPath(branchSpecificPath: string, branchName: s
|
|||||||
const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_');
|
const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_');
|
||||||
const escapedBranchName = sanitizedBranchName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
const escapedBranchName = sanitizedBranchName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
|
||||||
// Check for resource file pattern first
|
// Check for folder meta file pattern: /folder.branchName.meta.yaml -> /folder.meta.yaml
|
||||||
|
const folderPattern = new RegExp(`/folder\\.${escapedBranchName}\\.meta\\.yaml$`);
|
||||||
|
if (folderPattern.test(branchSpecificPath)) {
|
||||||
|
return branchSpecificPath.replace(folderPattern, '/folder.meta.yaml');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for settings file pattern: settings.branchName.yaml -> settings.yaml
|
||||||
|
const settingsPattern = new RegExp(`^settings\\.${escapedBranchName}\\.yaml$`);
|
||||||
|
if (settingsPattern.test(branchSpecificPath)) {
|
||||||
|
return 'settings.yaml';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for resource file pattern
|
||||||
const resourceFilePattern = new RegExp(`\\.${escapedBranchName}(\\.resource\\.file\\..+)$`);
|
const resourceFilePattern = new RegExp(`\\.${escapedBranchName}(\\.resource\\.file\\..+)$`);
|
||||||
const resourceFileMatch = branchSpecificPath.match(resourceFilePattern);
|
const resourceFileMatch = branchSpecificPath.match(resourceFilePattern);
|
||||||
|
|
||||||
@@ -283,7 +372,12 @@ export function isCurrentBranchFile(path: string, branchOverride?: string): bool
|
|||||||
// Use cached pattern or create and cache new one
|
// Use cached pattern or create and cache new one
|
||||||
let pattern = branchPatternCache.get(currentBranch);
|
let pattern = branchPatternCache.get(currentBranch);
|
||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
pattern = new RegExp(`\\.${escapedBranchName}\\.${buildYamlTypePattern()}\\.yaml$|\\.${escapedBranchName}\\.resource\\.file\\..+$`);
|
pattern = new RegExp(
|
||||||
|
`\\.${escapedBranchName}\\.${buildYamlTypePattern()}\\.yaml$|` +
|
||||||
|
`\\.${escapedBranchName}\\.resource\\.file\\..+$|` +
|
||||||
|
`/folder\\.${escapedBranchName}\\.meta\\.yaml$|` +
|
||||||
|
`^settings\\.${escapedBranchName}\\.yaml$`
|
||||||
|
);
|
||||||
branchPatternCache.set(currentBranch, pattern);
|
branchPatternCache.set(currentBranch, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,5 +390,10 @@ export function isCurrentBranchFile(path: string, branchOverride?: string): bool
|
|||||||
*/
|
*/
|
||||||
export function isBranchSpecificFile(path: string): boolean {
|
export function isBranchSpecificFile(path: string): boolean {
|
||||||
const yamlTypePattern = buildYamlTypePattern();
|
const yamlTypePattern = buildYamlTypePattern();
|
||||||
return new RegExp(`\\.[^.]+\\.${yamlTypePattern}\\.yaml$|\\.[^.]+\\.resource\\.file\\..+$`).test(path);
|
return new RegExp(
|
||||||
|
`\\.[^.]+\\.${yamlTypePattern}\\.yaml$|` +
|
||||||
|
`\\.[^.]+\\.resource\\.file\\..+$|` +
|
||||||
|
`/folder\\.[^.]+\\.meta\\.yaml$|` +
|
||||||
|
`^settings\\.[^.]+\\.yaml$`
|
||||||
|
).test(path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ import { assertEquals, assertExists, assert } from "https://deno.land/std@0.224.
|
|||||||
// Import the functions we need to test
|
// Import the functions we need to test
|
||||||
import {
|
import {
|
||||||
isSpecificItem,
|
isSpecificItem,
|
||||||
|
isItemTypeConfigured,
|
||||||
toBranchSpecificPath,
|
toBranchSpecificPath,
|
||||||
fromBranchSpecificPath,
|
fromBranchSpecificPath,
|
||||||
isBranchSpecificFile,
|
isBranchSpecificFile,
|
||||||
|
isCurrentBranchFile,
|
||||||
|
getBranchSpecificPath,
|
||||||
|
getSpecificItemsForCurrentBranch,
|
||||||
} from "../src/core/specific_items.ts";
|
} from "../src/core/specific_items.ts";
|
||||||
|
|
||||||
import type { SpecificItemsConfig } from "../src/core/specific_items.ts";
|
import type { SpecificItemsConfig } from "../src/core/specific_items.ts";
|
||||||
@@ -212,12 +216,6 @@ Deno.test("round-trip: resource file with extension", () => {
|
|||||||
// These tests validate that functions work correctly with explicit branch override
|
// These tests validate that functions work correctly with explicit branch override
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import {
|
|
||||||
getBranchSpecificPath,
|
|
||||||
isCurrentBranchFile,
|
|
||||||
getSpecificItemsForCurrentBranch,
|
|
||||||
} from "../src/core/specific_items.ts";
|
|
||||||
|
|
||||||
Deno.test("branchOverride: getBranchSpecificPath with override returns branch-specific path", () => {
|
Deno.test("branchOverride: getBranchSpecificPath with override returns branch-specific path", () => {
|
||||||
// This test verifies that when branchOverride is provided, the function uses it
|
// This test verifies that when branchOverride is provided, the function uses it
|
||||||
// instead of detecting the current git branch
|
// instead of detecting the current git branch
|
||||||
@@ -336,3 +334,288 @@ Deno.test("branchOverride: getSpecificItemsForCurrentBranch merges common and br
|
|||||||
assertEquals(result?.resources, ["shared/**"]);
|
assertEquals(result?.resources, ["shared/**"]);
|
||||||
assertEquals(result?.triggers, ["dev/triggers/**"]);
|
assertEquals(result?.triggers, ["dev/triggers/**"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// FOLDER BRANCH-SPECIFIC TESTS
|
||||||
|
// Format: f/folder/folder.branchName.meta.yaml
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
Deno.test("toBranchSpecificPath: converts folder meta path to branch-specific", () => {
|
||||||
|
// f/my_folder/folder.meta.yaml -> f/my_folder/folder.main.meta.yaml
|
||||||
|
const result = toBranchSpecificPath("f/my_folder/folder.meta.yaml", "main");
|
||||||
|
assertEquals(result, "f/my_folder/folder.main.meta.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("toBranchSpecificPath: converts nested folder meta path to branch-specific", () => {
|
||||||
|
const result = toBranchSpecificPath("f/parent/child/folder.meta.yaml", "develop");
|
||||||
|
assertEquals(result, "f/parent/child/folder.develop.meta.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("toBranchSpecificPath: sanitizes branch name in folder path", () => {
|
||||||
|
const result = toBranchSpecificPath("f/env/folder.meta.yaml", "feature/test");
|
||||||
|
assertEquals(result, "f/env/folder.feature_test.meta.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fromBranchSpecificPath: converts branch-specific folder back to base", () => {
|
||||||
|
const result = fromBranchSpecificPath("f/my_folder/folder.main.meta.yaml", "main");
|
||||||
|
assertEquals(result, "f/my_folder/folder.meta.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fromBranchSpecificPath: handles nested branch-specific folder", () => {
|
||||||
|
const result = fromBranchSpecificPath("f/parent/child/folder.develop.meta.yaml", "develop");
|
||||||
|
assertEquals(result, "f/parent/child/folder.meta.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fromBranchSpecificPath: handles sanitized branch names for folders", () => {
|
||||||
|
const result = fromBranchSpecificPath("f/env/folder.feature_test.meta.yaml", "feature/test");
|
||||||
|
assertEquals(result, "f/env/folder.meta.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isSpecificItem: matches folder paths with glob pattern", () => {
|
||||||
|
const config: SpecificItemsConfig = {
|
||||||
|
folders: ["f/env_*"],
|
||||||
|
};
|
||||||
|
assertEquals(isSpecificItem("f/env_staging/folder.meta.yaml", config), true);
|
||||||
|
assertEquals(isSpecificItem("f/env_production/folder.meta.yaml", config), true);
|
||||||
|
assertEquals(isSpecificItem("f/other/folder.meta.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isSpecificItem: matches folder paths with exact pattern", () => {
|
||||||
|
const config: SpecificItemsConfig = {
|
||||||
|
folders: ["f/config"],
|
||||||
|
};
|
||||||
|
assertEquals(isSpecificItem("f/config/folder.meta.yaml", config), true);
|
||||||
|
assertEquals(isSpecificItem("f/other/folder.meta.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isBranchSpecificFile: detects branch-specific folder files", () => {
|
||||||
|
assertEquals(isBranchSpecificFile("f/my_folder/folder.main.meta.yaml"), true);
|
||||||
|
assertEquals(isBranchSpecificFile("f/my_folder/folder.develop.meta.yaml"), true);
|
||||||
|
assertEquals(isBranchSpecificFile("f/nested/path/folder.staging.meta.yaml"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isBranchSpecificFile: returns false for non-branch-specific folder files", () => {
|
||||||
|
assertEquals(isBranchSpecificFile("f/my_folder/folder.meta.yaml"), false);
|
||||||
|
assertEquals(isBranchSpecificFile("f/nested/path/folder.meta.yaml"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isCurrentBranchFile: detects branch-specific folder for current branch", () => {
|
||||||
|
assertEquals(isCurrentBranchFile("f/my_folder/folder.staging.meta.yaml", "staging"), true);
|
||||||
|
assertEquals(isCurrentBranchFile("f/my_folder/folder.staging.meta.yaml", "production"), false);
|
||||||
|
assertEquals(isCurrentBranchFile("f/my_folder/folder.meta.yaml", "staging"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isCurrentBranchFile: handles sanitized branch for folders", () => {
|
||||||
|
assertEquals(isCurrentBranchFile("f/env/folder.feature_test.meta.yaml", "feature/test"), true);
|
||||||
|
assertEquals(isCurrentBranchFile("f/env/folder.feature_test.meta.yaml", "feature/other"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("round-trip: folder meta path conversion", () => {
|
||||||
|
const original = "f/configs/env_folder/folder.meta.yaml";
|
||||||
|
const branch = "main";
|
||||||
|
const branchSpecific = toBranchSpecificPath(original, branch);
|
||||||
|
assertEquals(branchSpecific, "f/configs/env_folder/folder.main.meta.yaml");
|
||||||
|
const restored = fromBranchSpecificPath(branchSpecific, branch);
|
||||||
|
assertEquals(restored, original);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("round-trip: folder meta with sanitized branch", () => {
|
||||||
|
const original = "f/env/folder.meta.yaml";
|
||||||
|
const branch = "feature/new-env";
|
||||||
|
const branchSpecific = toBranchSpecificPath(original, branch);
|
||||||
|
assertEquals(branchSpecific, "f/env/folder.feature_new-env.meta.yaml");
|
||||||
|
const restored = fromBranchSpecificPath(branchSpecific, branch);
|
||||||
|
assertEquals(restored, original);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SETTINGS BRANCH-SPECIFIC TESTS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
Deno.test("toBranchSpecificPath: converts settings.yaml to branch-specific", () => {
|
||||||
|
const result = toBranchSpecificPath("settings.yaml", "main");
|
||||||
|
assertEquals(result, "settings.main.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("toBranchSpecificPath: sanitizes branch name in settings path", () => {
|
||||||
|
const result = toBranchSpecificPath("settings.yaml", "feature/test");
|
||||||
|
assertEquals(result, "settings.feature_test.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fromBranchSpecificPath: converts branch-specific settings back to base", () => {
|
||||||
|
const result = fromBranchSpecificPath("settings.main.yaml", "main");
|
||||||
|
assertEquals(result, "settings.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fromBranchSpecificPath: handles sanitized branch names for settings", () => {
|
||||||
|
const result = fromBranchSpecificPath("settings.feature_test.yaml", "feature/test");
|
||||||
|
assertEquals(result, "settings.yaml");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isSpecificItem: matches settings.yaml when settings is true", () => {
|
||||||
|
const config: SpecificItemsConfig = {
|
||||||
|
settings: true,
|
||||||
|
};
|
||||||
|
assertEquals(isSpecificItem("settings.yaml", config), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isSpecificItem: does not match settings.yaml when settings is false", () => {
|
||||||
|
const config: SpecificItemsConfig = {
|
||||||
|
settings: false,
|
||||||
|
};
|
||||||
|
assertEquals(isSpecificItem("settings.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isSpecificItem: does not match settings.yaml when settings is undefined", () => {
|
||||||
|
const config: SpecificItemsConfig = {
|
||||||
|
variables: ["f/**"],
|
||||||
|
};
|
||||||
|
assertEquals(isSpecificItem("settings.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isBranchSpecificFile: detects branch-specific settings files", () => {
|
||||||
|
assertEquals(isBranchSpecificFile("settings.main.yaml"), true);
|
||||||
|
assertEquals(isBranchSpecificFile("settings.develop.yaml"), true);
|
||||||
|
assertEquals(isBranchSpecificFile("settings.feature_test.yaml"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isBranchSpecificFile: returns false for non-branch-specific settings", () => {
|
||||||
|
assertEquals(isBranchSpecificFile("settings.yaml"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isCurrentBranchFile: detects branch-specific settings for current branch", () => {
|
||||||
|
assertEquals(isCurrentBranchFile("settings.staging.yaml", "staging"), true);
|
||||||
|
assertEquals(isCurrentBranchFile("settings.staging.yaml", "production"), false);
|
||||||
|
assertEquals(isCurrentBranchFile("settings.yaml", "staging"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isCurrentBranchFile: handles sanitized branch for settings", () => {
|
||||||
|
assertEquals(isCurrentBranchFile("settings.feature_test.yaml", "feature/test"), true);
|
||||||
|
assertEquals(isCurrentBranchFile("settings.feature_test.yaml", "feature/other"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("round-trip: settings path conversion", () => {
|
||||||
|
const original = "settings.yaml";
|
||||||
|
const branch = "main";
|
||||||
|
const branchSpecific = toBranchSpecificPath(original, branch);
|
||||||
|
assertEquals(branchSpecific, "settings.main.yaml");
|
||||||
|
const restored = fromBranchSpecificPath(branchSpecific, branch);
|
||||||
|
assertEquals(restored, original);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("round-trip: settings with sanitized branch", () => {
|
||||||
|
const original = "settings.yaml";
|
||||||
|
const branch = "release/v1.0";
|
||||||
|
const branchSpecific = toBranchSpecificPath(original, branch);
|
||||||
|
assertEquals(branchSpecific, "settings.release_v1_0.yaml");
|
||||||
|
const restored = fromBranchSpecificPath(branchSpecific, branch);
|
||||||
|
assertEquals(restored, original);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// isItemTypeConfigured TESTS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns true when folders is configured", () => {
|
||||||
|
const config: SpecificItemsConfig = { folders: ["f/**"] };
|
||||||
|
assertEquals(isItemTypeConfigured("f/test/folder.meta.yaml", config), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns false when folders is NOT configured", () => {
|
||||||
|
const config: SpecificItemsConfig = { variables: ["f/**"] };
|
||||||
|
assertEquals(isItemTypeConfigured("f/test/folder.meta.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns true when variables is configured", () => {
|
||||||
|
const config: SpecificItemsConfig = { variables: ["f/**"] };
|
||||||
|
assertEquals(isItemTypeConfigured("f/test.variable.yaml", config), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns false when variables is NOT configured", () => {
|
||||||
|
const config: SpecificItemsConfig = { folders: ["f/**"] };
|
||||||
|
assertEquals(isItemTypeConfigured("f/test.variable.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns true when settings is configured", () => {
|
||||||
|
const config: SpecificItemsConfig = { settings: true };
|
||||||
|
assertEquals(isItemTypeConfigured("settings.yaml", config), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns false when settings is NOT configured", () => {
|
||||||
|
const config: SpecificItemsConfig = { folders: ["f/**"] };
|
||||||
|
assertEquals(isItemTypeConfigured("settings.yaml", config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isItemTypeConfigured: returns false for undefined config", () => {
|
||||||
|
assertEquals(isItemTypeConfigured("f/test/folder.meta.yaml", undefined), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Branch-specific item filtering behavior tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
Deno.test("filtering: folder type configured and matches - should process branch-specific file", () => {
|
||||||
|
const config: SpecificItemsConfig = { folders: ["f/**"] };
|
||||||
|
const basePath = "f/compliance/folder.meta.yaml";
|
||||||
|
|
||||||
|
// Type is configured AND matches pattern = should process
|
||||||
|
const typeConfigured = isItemTypeConfigured(basePath, config);
|
||||||
|
const matchesPattern = isSpecificItem(basePath, config);
|
||||||
|
|
||||||
|
assertEquals(typeConfigured, true);
|
||||||
|
assertEquals(matchesPattern, true);
|
||||||
|
// Skip condition: typeConfigured && !matchesPattern = false (should NOT skip)
|
||||||
|
assertEquals(typeConfigured && !matchesPattern, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("filtering: folder type configured but does NOT match - should skip branch-specific file", () => {
|
||||||
|
const config: SpecificItemsConfig = { folders: ["f/other/**"] };
|
||||||
|
const basePath = "f/compliance/folder.meta.yaml";
|
||||||
|
|
||||||
|
// Type is configured but does NOT match pattern = should skip
|
||||||
|
const typeConfigured = isItemTypeConfigured(basePath, config);
|
||||||
|
const matchesPattern = isSpecificItem(basePath, config);
|
||||||
|
|
||||||
|
assertEquals(typeConfigured, true);
|
||||||
|
assertEquals(matchesPattern, false);
|
||||||
|
// Skip condition: typeConfigured && !matchesPattern = true (should skip)
|
||||||
|
assertEquals(typeConfigured && !matchesPattern, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("filtering: folder type NOT configured - should process branch-specific file", () => {
|
||||||
|
const config: SpecificItemsConfig = { variables: ["f/**"] };
|
||||||
|
const basePath = "f/compliance/folder.meta.yaml";
|
||||||
|
|
||||||
|
// Type is NOT configured = should process (not subject to specificItems rules)
|
||||||
|
const typeConfigured = isItemTypeConfigured(basePath, config);
|
||||||
|
|
||||||
|
assertEquals(typeConfigured, false);
|
||||||
|
// Skip condition: typeConfigured && !matchesPattern = false (should NOT skip)
|
||||||
|
assertEquals(typeConfigured && !isSpecificItem(basePath, config), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("filtering: getSpecificItemsForCurrentBranch returns correct config", () => {
|
||||||
|
const config = {
|
||||||
|
gitBranches: {
|
||||||
|
dev: {
|
||||||
|
specificItems: {
|
||||||
|
folders: ["f/**"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prod: {
|
||||||
|
specificItems: {
|
||||||
|
folders: ["f/prod/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const devItems = getSpecificItemsForCurrentBranch(config as any, "dev");
|
||||||
|
assertEquals(devItems?.folders, ["f/**"]);
|
||||||
|
|
||||||
|
const prodItems = getSpecificItemsForCurrentBranch(config as any, "prod");
|
||||||
|
assertEquals(prodItems?.folders, ["f/prod/**"]);
|
||||||
|
|
||||||
|
const unknownItems = getSpecificItemsForCurrentBranch(config as any, "unknown");
|
||||||
|
assertEquals(unknownItems, undefined);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user