"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.templates = exports.renderTemplates = exports.ProjectInitValidation = exports.ProjectFactory = void 0;
exports.wouldTemplateConflict = wouldTemplateConflict;
const sdk_build_core_1 = require("@servicenow/sdk-build-core");
const os = __importStar(require("os"));
const project_1 = require("../project");
const util_1 = require("../util");
const context_1 = require("../context");
const templates_1 = require("./templates");
Object.defineProperty(exports, "templates", { enumerable: true, get: function () { return templates_1.templates; } });
const template_package_1 = require("./scaffolders/template-package");
const merge_1 = __importDefault(require("lodash/merge"));
const handlebars_1 = __importDefault(require("handlebars"));
const ALLOWED_FILES_FOR_MERGE = ['package.json', 'now.config.json', '.gitignore', 'aiux.json'];
function detectIndent(raw) {
    for (const line of raw.split('\n').slice(1)) {
        const match = line.match(/^(\s+)/);
        if (match) {
            const ws = match[1];
            return ws.includes('\t') ? '\t' : ws.length;
        }
    }
    return 2;
}
const BUILD_AGENT_CATEGORY = 'Build Agent';
function matchesQuery(info, query) {
    if (query?.label !== undefined) {
        return info.labels?.includes(query.label) ?? false;
    }
    return true;
}
function isCategoryVisible(info) {
    if (info.category === BUILD_AGENT_CATEGORY) {
        return Boolean(process.env['__FORGE_ENVIRONMENT']);
    }
    return true;
}
const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
class ProjectFactory {
    fs;
    project;
    telemetry;
    registry;
    templatePackageAvailability;
    constructor(fs, telemetry, project, options) {
        this.fs = fs;
        this.project = project;
        this.telemetry = telemetry ?? sdk_build_core_1.TelemetryFactory.create();
        this.registry = options?.registry ?? process.env['npm_config_registry'] ?? DEFAULT_NPM_REGISTRY;
    }
    async preloadTemplatePackages() {
        const availability = new Map();
        const checks = [];
        for (const [, template] of Object.entries(templates_1.templates)) {
            const pkg = template.info.templatePackage;
            if (pkg) {
                checks.push((0, template_package_1.isTemplatePackageAvailable)(pkg, this.registry).then((available) => {
                    availability.set(pkg.name, available);
                }));
            }
        }
        await Promise.allSettled(checks);
        this.templatePackageAvailability = availability;
    }
    isTemplateAvailable(template) {
        const pkg = template.info.templatePackage;
        if (!pkg) {
            return true;
        }
        if (!this.templatePackageAvailability) {
            return true;
        }
        return this.templatePackageAvailability.get(pkg.name) ?? true;
    }
    getProjectTemplates(query) {
        return Object.entries(templates_1.templates)
            .filter(([, template]) => !template.info.partial && !template.info.configuration && template.info.category !== 'inplace')
            .filter(([, template]) => isCategoryVisible(template.info))
            .filter(([, template]) => this.isTemplateAvailable(template))
            .filter(([, template]) => matchesQuery(template.info, query))
            .map(([id, template]) => ({
            ...template.info,
            id,
        }))
            .sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
    }
    getPartialTemplates(query) {
        return Object.entries(templates_1.templates)
            .filter(([, template]) => template.info.partial)
            .filter(([, template]) => isCategoryVisible(template.info))
            .filter(([, template]) => this.isTemplateAvailable(template))
            .filter(([, template]) => !this.wouldConflict(template))
            .filter(([, template]) => matchesQuery(template.info, query))
            .map(([id, template]) => ({
            ...template.info,
            id,
        }))
            .sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
    }
    wouldConflict(template) {
        if (!this.project) {
            return false;
        }
        const config = this.project.getConfig();
        const rootDir = this.project.getRootDir();
        const renderedTemplates = (0, exports.renderTemplates)(template.files, { ...config }, this.getOriginalFiles(rootDir));
        for (const renderedTemplate of renderedTemplates) {
            const absolutePath = sdk_build_core_1.path.join(rootDir, renderedTemplate.path);
            if (!sdk_build_core_1.FileSystem.existsSync(this.fs, absolutePath)) {
                continue;
            }
            const existingContent = this.fs.readFileSync(absolutePath, { encoding: 'utf-8' });
            if (wouldTemplateConflict(renderedTemplate.templateFile.content, existingContent, renderedTemplate.content)) {
                return true;
            }
        }
        return false;
    }
    getConfigurationTemplate() {
        return Object.entries(templates_1.templates)
            .filter(([, template]) => template.info.configuration)
            .map(([id, template]) => ({
            ...template.info,
            id,
        }))
            .sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
    }
    getOriginalFiles(workingDir) {
        const originalFiles = {};
        for (const file of ALLOWED_FILES_FOR_MERGE) {
            const filePath = sdk_build_core_1.path.join(workingDir, file);
            if (sdk_build_core_1.FileSystem.existsSync(this.fs, filePath)) {
                originalFiles[file] = this.fs.readFileSync(filePath, { encoding: 'utf-8' });
            }
        }
        return originalFiles;
    }
    async createProjectFromApp(workingDir, scopeId, connector, options) {
        try {
            const telemetryTimer = this.telemetry.startTimerEvent('init.from-instance');
            const metadata = await connector.download(scopeId, { method: 'complete' });
            if (!metadata.ok || !metadata.body) {
                const body = await metadata.json();
                if (body && typeof body.result === 'string') {
                    throw new Error(`Application download failed: ${body.result}`);
                }
                throw new Error(`Failed to download package for ${scopeId}`);
            }
            const tempDir = sdk_build_core_1.path.join(os.tmpdir(), (0, sdk_build_core_1.GUID)());
            this.fs.mkdirSync(tempDir, { recursive: true });
            const zipFile = sdk_build_core_1.path.join(tempDir, 'metadata.zip');
            await (0, util_1.writeFileStream)(this.fs, zipFile, metadata.body);
            const unzipDir = sdk_build_core_1.path.join(tempDir, 'metadata');
            await (0, sdk_build_core_1.unzipAppPackage)(this.fs, zipFile, unzipDir, this.project?.getLogger());
            const projectPromise = this.createProjectFromDirectory0(workingDir, unzipDir, options);
            this.telemetry.sendEvent(telemetryTimer.end({
                data: { success: true, from: scopeId },
            }));
            return projectPromise;
        }
        catch (error) {
            this.telemetry.error('init.from-instance', error);
            throw error;
        }
    }
    async createConfigurationProject(workingDir, connector, scopeOptions, options) {
        const { scope, scopeId } = scopeOptions;
        const scopeResult = await connector.getScopeInfo(scope, scopeId ?? (scope === 'global' ? 'global' : undefined));
        if (!scopeResult) {
            throw new Error(`Scope must be present on instance to configure customizations `);
        }
        const projectSettings = {
            scopeId: scopeId ?? scopeResult?.appId,
            name: scopeResult.name,
            scope: scope ?? scopeResult.scope,
            packageName: options.packageName ??
                ProjectFactory.createNpmPackageName(`${scope ?? scopeResult.scope}-configuration`),
            description: scopeResult.shortDescription ?? '',
            sdkVersion: options.sdkVersion || 'latest',
            projectVersion: '0.0.1',
        };
        const errors = exports.ProjectInitValidation.validateInitOptions(projectSettings);
        if (errors.length) {
            throw new Error(errors.join(', '));
        }
        const blankTemplateFiles = (0, exports.renderTemplates)(templates_1.templates.configuration.files, projectSettings, {});
        this.writeTemplates(workingDir, blankTemplateFiles);
        const proj = new project_1.Project({ fileSystem: this.fs, rootDir: workingDir, telemetryClient: this.telemetry });
        return proj;
    }
    async createProjectFromDirectory(workingDir, metadataDir, options) {
        try {
            const telemetryTimer = this.telemetry.startTimerEvent('init.from-fs');
            const proj = await this.createProjectFromDirectory0(workingDir, metadataDir, options);
            const { scopeId } = proj.getConfig();
            this.telemetry.sendEvent(telemetryTimer.end({
                data: { success: true, from: scopeId },
            }));
            return proj;
        }
        catch (error) {
            this.telemetry.error('init.from-fs', error);
            throw error;
        }
    }
    async createProjectFromDirectory0(workingDir, metadataDir, options) {
        const initInPlace = sdk_build_core_1.FileSystem.existsSync(this.fs, sdk_build_core_1.path.join(workingDir, metadataDir));
        const existingPackageJson = sdk_build_core_1.FileSystem.existsSync(this.fs, sdk_build_core_1.path.join(workingDir, 'package.json'));
        const sysAppXml = await this.findSysAppXmlInformation(metadataDir);
        const pluginProperties = await this.findPluginPropertiesInfo(workingDir);
        if (!sysAppXml) {
            throw new Error('Failed to find a sys app xml file in application under path: ' + metadataDir);
        }
        const scope = pluginProperties?.scope ?? sysAppXml.scope;
        const projectSettings = {
            scopeId: pluginProperties?.scopeId ?? sysAppXml.scopeId,
            name: pluginProperties?.name ?? sysAppXml.name,
            scope: scope,
            packageName: options.packageName || ProjectFactory.createNpmPackageName(pluginProperties?.name ?? sysAppXml.name),
            description: pluginProperties?.description ?? sysAppXml.shortDescription ?? '',
            sdkVersion: options.sdkVersion || 'latest',
            projectVersion: sysAppXml.version ?? '0.0.1',
            metadataDir: initInPlace ? metadataDir : undefined,
            conversion: options.conversion,
        };
        const errors = exports.ProjectInitValidation.validateInitOptions(projectSettings);
        if (errors.length) {
            throw new Error(errors.join(', '));
        }
        const templateFiles = initInPlace ? templates_1.templates['inplace-conversion'].files : templates_1.templates.base.files;
        const blankTemplateFiles = (0, exports.renderTemplates)(existingPackageJson
            ? templateFiles.filter((f) => f.path !== 'package.json' || isMergeableTemplate(f.content))
            : templateFiles, projectSettings, this.getOriginalFiles(workingDir));
        if (!existingPackageJson && sysAppXml.packageJson) {
            const packageJsonTemplate = blankTemplateFiles.filter((f) => f.path === 'package.json');
            const sysModuleFile = this.findPackageJsonModule(metadataDir, sysAppXml.packageJson);
            if (!sysModuleFile) {
                throw new Error(`The sys_app xml file contains package_json field but no matching package.json sys_module was found in the application.`);
            }
            packageJsonTemplate[0].content = sysModuleFile;
        }
        this.writeTemplates(workingDir, blankTemplateFiles);
        const proj = new project_1.Project({ fileSystem: this.fs, rootDir: workingDir, telemetryClient: this.telemetry });
        if (scope.length > 18) {
            proj.getLogger().warn(`Scope name too long: Scope will be truncated by the instance to 18 characters`);
        }
        sdk_build_core_1.FileSystem.copySync(this.fs, metadataDir, proj.getMetadataDir(), {
            filter: (file) => !file.startsWith('sys_module'),
        });
        return proj;
    }
    findPackageJsonModule(metadataDir, packageJson) {
        let packageJsonModule;
        const sysModuleFiles = [];
        (0, util_1.traverseDirectory)(this.fs, metadataDir, {
            visitor: (file) => {
                if (sdk_build_core_1.path.basename(file).startsWith('sys_module')) {
                    sysModuleFiles.push(file);
                }
            },
            extensions: ['.xml'],
        });
        for (const sysModuleFile of sysModuleFiles) {
            const content = this.fs.readFileSync(sysModuleFile, { encoding: 'utf-8' });
            if (content.includes(packageJson)) {
                const cdataRegex = /<content><!\[CDATA\[([\s\S]*?)\]\]><\/content>/;
                const match = content.match(cdataRegex);
                if (match && match[1]) {
                    packageJsonModule = match[1];
                    break;
                }
            }
        }
        return packageJsonModule;
    }
    async createProject(workingDir, options) {
        try {
            const telemetryTimer = this.telemetry.startTimerEvent('createProject');
            const errors = exports.ProjectInitValidation.validateInitOptions(options);
            if (errors.length) {
                throw new Error(errors.join(', '));
            }
            const projectSettings = {
                ...options,
                scopeId: options.scopeId || (0, sdk_build_core_1.GUID)(),
            };
            const blankTemplateFiles = (0, exports.renderTemplates)(templates_1.templates.base.files, projectSettings, {});
            this.writeTemplates(workingDir, blankTemplateFiles);
            if (projectSettings.templateId && projectSettings.templateId !== 'base') {
                const template = templates_1.templates[projectSettings.templateId];
                if (template.info.templatePackage) {
                    const { files } = await (0, template_package_1.scaffoldFromTemplatePackage)(workingDir, this.fs, template.info.templatePackage, this.registry);
                    this.renderExtractedFiles(workingDir, files, {
                        ...projectSettings,
                        basename: projectSettings.packageName,
                    });
                }
                const originalFiles = this.getOriginalFiles(workingDir);
                const templateRenderedFiles = (0, exports.renderTemplates)(template.files, projectSettings, originalFiles);
                this.writeTemplates(workingDir, templateRenderedFiles);
            }
            const project = new project_1.Project({ fileSystem: this.fs, rootDir: workingDir, telemetryClient: this.telemetry });
            this.telemetry.sendEvent(telemetryTimer.end({
                data: {
                    success: true,
                    ...options,
                },
            }));
            return project;
        }
        catch (error) {
            this.telemetry.error('createProject', error);
            throw error;
        }
    }
    async findSysAppXmlInformation(directory) {
        const sysAppPath = this.getSysAppXML(directory);
        if (!sysAppPath) {
            return undefined;
        }
        const parser = new context_1.Parser(new project_1.Project({ config: {} }), [], []);
        const [record] = await parser.parsePayload({
            path: sysAppPath,
            content: this.fs.readFileSync(sysAppPath).toString('utf-8'),
        });
        if (!record) {
            throw new Error(`Error parsing ${sysAppPath}. Check the sys_app XML file and try again.`);
        }
        return {
            name: record.get('name').asString('sys_app record must have a name').getValue(),
            version: record.get('version').asString('sys_app record must have a version').getValue(),
            shortDescription: record.get('short_description').ifString()?.getValue(),
            scope: record.get('scope').asString('sys_app record must have a scope').getValue(),
            scopeId: record.getId().getValue(),
            packageJson: record.get('package_json').ifString()?.getValue(),
        };
    }
    async findPluginPropertiesInfo(workingDir) {
        const propertiesPath = sdk_build_core_1.path.join(workingDir, 'plugin.properties');
        if (!sdk_build_core_1.FileSystem.existsSync(this.fs, propertiesPath)) {
            return;
        }
        const content = this.fs.readFileSync(propertiesPath).toString('utf-8').split('\n');
        const props = {};
        for (const line of content) {
            const split = line.trim().split('=', 2);
            if (split.length === 2) {
                const propName = split[0];
                props[propName] = split[1];
            }
        }
        return {
            name: props['plugin.name'],
            scope: props['plugin.scope_name'],
            scopeId: props['plugin.scope_sys_id'],
            description: props['plugin.description'],
        };
    }
    getSysAppXML(directory) {
        const appRegex = /^sys_app_[0-9a-zA-Z]{32}\.xml$/;
        const storeAppRegex = /^sys_store_app_[0-9a-zA-Z]{32}\.xml$/;
        // sys_app or sys_store_app file can be in root metadata
        // directory, the scope directory, or within the update directory
        const findAppXml = (subdir, prefix) => {
            const dir = sdk_build_core_1.path.join(directory, subdir);
            if (sdk_build_core_1.FileSystem.existsSync(this.fs, dir)) {
                const dirItems = this.fs.readdirSync(dir);
                const appXml = dirItems.find((item) => prefix.test(item));
                return appXml && sdk_build_core_1.path.join(dir, appXml);
            }
            return undefined;
        };
        const rootDir = '';
        return (findAppXml(rootDir, appRegex) ||
            findAppXml('update', appRegex) ||
            findAppXml('scope', appRegex) ||
            findAppXml(rootDir, storeAppRegex) ||
            findAppXml('update', storeAppRegex) ||
            findAppXml('scope', storeAppRegex));
    }
    renderExtractedFiles(workingDir, files, data) {
        const hbs = handlebars_1.default.create();
        // Helpers registered on the global Handlebars singleton (see renderTemplates)
        // are NOT inherited by an instance created with Handlebars.create(). Register
        // the same shared helpers directly on this isolated instance, otherwise
        // compilation throws "Missing helper: ...".
        registerTemplateHelpers(hbs, this.getOriginalFiles(workingDir));
        const originalCompile = hbs.compile.bind(hbs);
        hbs.compile = (input, options) => 
        // To avoid handlebars failing when it tries to parse an object expression like ${{}} from a lit template
        originalCompile(input.replaceAll('${{', '$\\{{'), options);
        for (const filePath of files) {
            const content = this.fs.readFileSync(filePath, { encoding: 'utf-8' });
            const rendered = hbs.compile(content)(data);
            const outputPath = filePath.replace(/\.hbs$/, '');
            this.fs.writeFileSync(outputPath, rendered);
            if (outputPath !== filePath) {
                this.fs.rmSync(filePath, { force: true });
            }
        }
    }
    writeTemplates(directory, templates) {
        for (const [, template] of Object.entries(templates)) {
            const filePath = sdk_build_core_1.path.join(directory, template.path);
            this.fs.mkdirSync(sdk_build_core_1.path.dirname(filePath), { recursive: true });
            this.fs.writeFileSync(filePath, template.content);
        }
    }
    static createNpmPackageName(appName) {
        // Convert to lowercase
        let name = appName.toLowerCase();
        // Replace invalid characters with a hyphen
        name = name.replace(/[^a-z0-9-_.]+/g, '-');
        //truncate to 210 characters
        name = name.substring(0, 210);
        // Remove leading or trailing hyphens
        name = name.replace(/^-+|-+$/g, '');
        return name;
    }
}
exports.ProjectFactory = ProjectFactory;
exports.ProjectInitValidation = {
    validateInitOptions(options) {
        const errors = [];
        const appNameError = exports.ProjectInitValidation.validateAppName(options.name);
        if (appNameError) {
            errors.push(`appName ${appNameError}`);
        }
        const scopeNameError = exports.ProjectInitValidation.validateScopeName(options.scope, undefined, options.conversion);
        if (scopeNameError) {
            errors.push(`scopeName ${scopeNameError}`);
        }
        const packageNameError = exports.ProjectInitValidation.validateNpmPackageName(options.packageName);
        if (packageNameError) {
            errors.push(`packageName ${packageNameError}`);
        }
        return errors;
    },
    validateAppName(name) {
        if (!name) {
            return `cannot be empty`;
        }
        if (name.length > 100) {
            return `exceeds 100 characters`;
        }
    },
    validateScopeName(name, requiredPrefix, conversion = false) {
        const errors = [];
        if (!name) {
            return `cannot be empty`;
        }
        if (name.length < 4 && !conversion) {
            errors.push('must be at least 4 characters long');
        }
        if (name.length > 18 && !conversion) {
            errors.push('must be less than 19 characters');
        }
        if (name.endsWith('_')) {
            errors.push('cannot end in an underscore');
        }
        if (name.includes('__')) {
            errors.push('cannot contain double underscore');
        }
        if (!conversion && /[^a-z0-9_]/.test(name)) {
            errors.push('can only contain lowercase alphanumeric characters and underscores');
        }
        // Matching NowConfig validation
        if (!conversion && !/^((x|sn)_[a-z0-9_]+|global)$/.test(name)) {
            errors.push(`must match pattern 'x/sn_<vendor>_<name>'`);
        }
        if (requiredPrefix) {
            if (name === requiredPrefix) {
                errors.push(`must not exactly match prefix "${requiredPrefix}"`);
            }
            if (!name.startsWith(requiredPrefix)) {
                errors.push(`must start with prefix "${requiredPrefix}"`);
            }
        }
        if (errors.length) {
            const listJoin = new Intl.ListFormat('default', {
                style: 'long',
                type: 'conjunction',
            });
            return `Invalid scope: ${listJoin.format(errors)}`;
        }
    },
    validateNpmPackageName(name) {
        if (!name) {
            return `cannot be empty`;
        }
        if (name.length > 214) {
            return 'name must be less than or equal to 214 characters';
        }
        // mIxeD CaSe nAMEs
        if (name.toLowerCase() !== name) {
            return 'name can no longer contain capital letters';
        }
        if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
            return 'name can no longer contain special characters ("~\'!()*")';
        }
        if (encodeURIComponent(name) !== name) {
            // Maybe it's a scoped package name, like @user/package
            const nameMatch = name.match(/@([^/]+)\/([^/]+)/);
            if (nameMatch) {
                const user = nameMatch[1];
                const pkg = nameMatch[2];
                if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
                    return;
                }
            }
            return 'name can only contain URL-friendly characters';
        }
    },
};
function isMergeableTemplate(templateContent) {
    const parsed = handlebars_1.default.parseWithoutProcessing(templateContent);
    return parsed.body.some((b) => b.type === 'BlockStatement' &&
        ['mergeJsonFiles', 'mergeTextFile'].includes(b.path.original));
}
function wouldTemplateConflict(templateContent, existingContent, newContent) {
    return !isMergeableTemplate(templateContent) && existingContent !== newContent;
}
// Shared Handlebars helpers used both when rendering base/layered templates (renderTemplates)
// and when rendering files extracted from a template package (renderExtractedFiles). Registering
// them in one place keeps the two render paths in sync — a Handlebars instance created via
// `Handlebars.create()` does not inherit helpers registered on the global singleton.
function registerTemplateHelpers(hbs, accumulator) {
    // Explicitly type `this` to satisfy the `noImplicitThis` TypeScript compiler option.
    hbs.registerHelper('mergeJsonFiles', function (filePath, options) {
        const patchJson = options.fn(this).trim();
        const defaultsJson = options.inverse(this).trim();
        const originalRaw = accumulator[filePath] || '{}';
        let patch;
        let defaults;
        let originalJson;
        try {
            patch = JSON.parse(patchJson || '{}');
            defaults = JSON.parse(defaultsJson || '{}');
            originalJson = JSON.parse(originalRaw);
        }
        catch (e) {
            throw new Error(`Invalid JSON in mergeJsonFiles block:\n${e?.message}`);
        }
        const withDefaults = { ...originalJson };
        for (const [key, value] of Object.entries(defaults)) {
            const current = withDefaults[key];
            if (current === undefined || current === null || current === '') {
                withDefaults[key] = value;
            }
        }
        const merged = (0, merge_1.default)(withDefaults, patch);
        return JSON.stringify(merged, null, detectIndent(originalRaw));
    });
    hbs.registerHelper('mergeTextFile', function (filePath, options) {
        const patch = options.fn(this).trim();
        const existing = accumulator[filePath] ?? '';
        if (!existing) {
            return patch;
        }
        const existingLines = existing.split('\n').map((l) => l.trimEnd());
        const existingLineSet = new Set(existingLines);
        const newLines = patch
            .split('\n')
            .map((l) => l.trimEnd())
            .filter((l) => !existingLineSet.has(l));
        if (newLines.length === 0) {
            return existing;
        }
        return [...existingLines, ...newLines].join('\n');
    });
    hbs.registerHelper('underscoresToHyphens', (str) => typeof str === 'string' ? str.replace(/_/g, '-') : str);
}
// templates used during convert/create
const renderTemplates = (templates, data, originalFiles) => {
    // Accumulator carries the running output as files are rendered in order.
    // This enables two distinct behaviours depending on file type:
    //   - Mergeable files ({{#mergeJsonFiles}} blocks, e.g. package.json, now.config.json):
    //     each occurrence reads from the accumulator so patches from multiple layers compose
    //     — the final write contains all contributions from all layers in order.
    //   - Non-mergeable files (plain content): the accumulator entry is updated but not read
    //     by the template; writeTemplates writes every occurrence and the last one wins.
    //     Layer ordering therefore determines which layer's version is kept.
    const accumulator = { ...originalFiles };
    registerTemplateHelpers(handlebars_1.default, accumulator);
    // Computed template variables
    const scope = typeof data['scope'] === 'string' ? data['scope'] : '';
    const scopePrefix = scope && scope !== 'global' ? `${scope}_` : '';
    const templateData = { ...data, scopePrefix };
    const output = [];
    for (const templateFile of templates) {
        const compiledPathTemplate = handlebars_1.default.compile(templateFile.path);
        const renderedPath = compiledPathTemplate({
            ...templateData,
        });
        // content template can use mergeJsonFiles helper
        const compiledContentTemplate = handlebars_1.default.compile(templateFile.content);
        const content = compiledContentTemplate({
            ...templateData,
        });
        output.push({
            templateFile,
            path: renderedPath,
            content,
        });
        // Update accumulator so subsequent templates for the same path see the merged result
        accumulator[renderedPath] = content;
    }
    return output;
};
exports.renderTemplates = renderTemplates;
//# sourceMappingURL=index.js.map