"use strict";
// src/test_scaffold_engine.ts
// v1.16.0 — Scaffold engine regression. Asserts:
//   1. TemplateRegistry discovers bundled templates from cwd.
//   2. ScaffoldEngine applies templates with {{token}} substitution.
//   3. Manifest exclude[] is honored.
//   4. Token-in-filename rename (__var__ → value) works.
//   5. Empty-target enforcement (force: false default).
//   6. dry-run skips writes but still reports.
//   7. Post-install steps get substitution applied.
//   8. Bundled nextjs-supabase-clerk-stripe smoke (real template).
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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const yaml = __importStar(require("js-yaml"));
const TemplateRegistry_1 = require("./orchestrator/scaffold/TemplateRegistry");
const ScaffoldEngine_1 = require("./orchestrator/scaffold/ScaffoldEngine");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function mkTmp(prefix = 'scaffold-test-') {
    return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
function rmTmp(dir) {
    try {
        fs.rmSync(dir, { recursive: true, force: true });
    }
    catch { /* */ }
}
function seedSyntheticTemplate(stateDir, id) {
    const tplDir = path.join(stateDir, 'templates', id);
    fs.mkdirSync(tplDir, { recursive: true });
    const manifest = {
        id,
        name: `Test ${id}`,
        description: 'A synthetic template for tests.',
        stack: { framework: 'test' },
        required_env: ['TEST_KEY'],
        post_install_steps: [
            'cd {{project_name}}',
            'echo "{{description}}"',
        ],
        exclude: ['ignored.txt', 'private/'],
    };
    fs.writeFileSync(path.join(tplDir, 'manifest.yaml'), yaml.dump(manifest));
    fs.writeFileSync(path.join(tplDir, 'package.json'), JSON.stringify({ name: '{{project_name}}' }, null, 2));
    fs.writeFileSync(path.join(tplDir, 'README.md'), '# {{project_name}}\n\n{{description}}\n');
    fs.writeFileSync(path.join(tplDir, '__project_name__.config.ts'), 'export const NAME = "{{project_name}}";\n');
    fs.writeFileSync(path.join(tplDir, 'ignored.txt'), 'should not be copied');
    fs.mkdirSync(path.join(tplDir, 'private'));
    fs.writeFileSync(path.join(tplDir, 'private', 'secret.txt'), 'nope');
    return tplDir;
}
function withStateDir(stateDir, fn) {
    const prev = process.env.OPENLIFE_STATE_DIR;
    process.env.OPENLIFE_STATE_DIR = stateDir;
    try {
        return fn();
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
    }
}
function scenarioRegistryDiscovers() {
    const stateDir = mkTmp();
    try {
        withStateDir(stateDir, () => {
            seedSyntheticTemplate(stateDir, 'tpl-a');
            seedSyntheticTemplate(stateDir, 'tpl-b');
            const reg = new TemplateRegistry_1.TemplateRegistry();
            const all = reg.list();
            const ids = all.map(t => t.id);
            assert(ids.includes('tpl-a'), 'tpl-a discovered');
            assert(ids.includes('tpl-b'), 'tpl-b discovered');
            for (const t of all.filter(x => x.id.startsWith('tpl-'))) {
                assert(t.source === 'user', `user template marked source=user, got ${t.source}`);
                assert(t.rootDir !== undefined, 'rootDir populated');
            }
        });
    }
    finally {
        rmTmp(stateDir);
    }
    console.log('✅ scenario 1: TemplateRegistry discovers user templates');
}
function scenarioApplyWithSubstitution() {
    const stateDir = mkTmp();
    const target = mkTmp('scaffold-target-');
    try {
        withStateDir(stateDir, () => {
            seedSyntheticTemplate(stateDir, 'subst');
            // Empty the target so apply succeeds (mkTmp created it).
            fs.rmSync(target, { recursive: true });
            const engine = new ScaffoldEngine_1.ScaffoldEngine();
            const result = engine.apply('subst', target, {
                project_name: 'my-app',
                description: 'My SaaS',
            });
            assert(result.ok, `apply should succeed, errors: ${result.errors.join('|')}`);
            assert(result.filesCopied >= 3, `should copy at least 3 files (package, README, renamed config), got ${result.filesCopied}`);
            const pkg = JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf-8'));
            assert(pkg.name === 'my-app', `package.json name substituted to my-app, got ${pkg.name}`);
            const readme = fs.readFileSync(path.join(target, 'README.md'), 'utf-8');
            assert(readme.includes('# my-app'), `README has project name`);
            assert(readme.includes('My SaaS'), `README has description`);
            // Renamed file
            assert(fs.existsSync(path.join(target, 'my-app.config.ts')), 'renamed config file present');
            assert(!fs.existsSync(path.join(target, '__project_name__.config.ts')), 'original token-named file should not exist');
        });
    }
    finally {
        rmTmp(stateDir);
        rmTmp(target);
    }
    console.log('✅ scenario 2: apply substitutes content + renames token-named files');
}
function scenarioExcludeHonored() {
    const stateDir = mkTmp();
    const target = mkTmp('scaffold-target-');
    try {
        withStateDir(stateDir, () => {
            seedSyntheticTemplate(stateDir, 'excl');
            fs.rmSync(target, { recursive: true });
            const result = new ScaffoldEngine_1.ScaffoldEngine().apply('excl', target, { project_name: 'x' });
            assert(result.ok, 'apply ok');
            assert(!fs.existsSync(path.join(target, 'ignored.txt')), 'ignored.txt excluded');
            assert(!fs.existsSync(path.join(target, 'private')), 'private/ excluded');
            assert(fs.existsSync(path.join(target, 'package.json')), 'package.json included');
        });
    }
    finally {
        rmTmp(stateDir);
        rmTmp(target);
    }
    console.log('✅ scenario 3: manifest.exclude[] honored');
}
function scenarioEmptyTargetEnforced() {
    const stateDir = mkTmp();
    const target = mkTmp('scaffold-target-');
    try {
        withStateDir(stateDir, () => {
            seedSyntheticTemplate(stateDir, 'empt');
            fs.writeFileSync(path.join(target, 'pre-existing'), 'hi');
            const engine = new ScaffoldEngine_1.ScaffoldEngine();
            const result = engine.apply('empt', target, { project_name: 'x' });
            assert(!result.ok, 'should refuse non-empty target');
            assert(result.errors.some(e => e.includes('target_not_empty')), `error must mention target_not_empty, got ${result.errors.join('|')}`);
            const forced = engine.apply('empt', target, { project_name: 'x', force: true });
            assert(forced.ok, `force:true should override, errors: ${forced.errors.join('|')}`);
        });
    }
    finally {
        rmTmp(stateDir);
        rmTmp(target);
    }
    console.log('✅ scenario 4: empty-target enforced; force:true overrides');
}
function scenarioDryRun() {
    const stateDir = mkTmp();
    const target = mkTmp('scaffold-target-');
    try {
        withStateDir(stateDir, () => {
            seedSyntheticTemplate(stateDir, 'dr');
            fs.rmSync(target, { recursive: true });
            const result = new ScaffoldEngine_1.ScaffoldEngine().apply('dr', target, { project_name: 'd', dryRun: true });
            assert(result.ok, 'dry-run ok');
            assert(result.filesCopied >= 3, 'dry-run counts what would be copied');
            assert(!fs.existsSync(path.join(target, 'package.json')), 'dry-run did NOT write package.json');
        });
    }
    finally {
        rmTmp(stateDir);
        rmTmp(target);
    }
    console.log('✅ scenario 5: dry-run reports counts without writing');
}
function scenarioPostInstallSubstituted() {
    const stateDir = mkTmp();
    const target = mkTmp('scaffold-target-');
    try {
        withStateDir(stateDir, () => {
            seedSyntheticTemplate(stateDir, 'post');
            fs.rmSync(target, { recursive: true });
            const result = new ScaffoldEngine_1.ScaffoldEngine().apply('post', target, { project_name: 'pi', description: 'PI app' });
            assert(result.postInstallSteps[0] === 'cd pi', `first step substituted, got "${result.postInstallSteps[0]}"`);
            assert(result.postInstallSteps[1].includes('PI app'), `description substituted in step, got "${result.postInstallSteps[1]}"`);
        });
    }
    finally {
        rmTmp(stateDir);
        rmTmp(target);
    }
    console.log('✅ scenario 6: post-install steps get substitution');
}
function scenarioBundledNextjs() {
    // Smoke against the real bundled template. Skipped when running outside repo.
    const repoTpl = path.join(process.cwd(), 'templates', 'nextjs-supabase-clerk-stripe', 'manifest.yaml');
    if (!fs.existsSync(repoTpl)) {
        console.log('✓ scenario 7 skipped (no bundled template in cwd)');
        return;
    }
    const target = mkTmp('scaffold-target-');
    try {
        fs.rmSync(target, { recursive: true });
        const result = new ScaffoldEngine_1.ScaffoldEngine().apply('nextjs-supabase-clerk-stripe', target, {
            project_name: 'smoke-app',
            description: 'Smoke test app',
        });
        assert(result.ok, `bundled template apply ok, errors: ${result.errors.join('|')}`);
        assert(result.filesCopied >= 20, `expected >=20 files, got ${result.filesCopied}`);
        // Key files must exist
        for (const f of ['package.json', 'middleware.ts', 'app/layout.tsx', 'lib/db.ts', 'supabase/migrations/0001_initial.sql']) {
            assert(fs.existsSync(path.join(target, f)), `expected ${f} after scaffold`);
        }
        // Substitution
        const pkg = JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf-8'));
        assert(pkg.name === 'smoke-app', `pkg name substituted, got ${pkg.name}`);
        // No leftover {{...}} in critical files
        const layoutSrc = fs.readFileSync(path.join(target, 'app/layout.tsx'), 'utf-8');
        assert(!layoutSrc.includes('{{'), 'layout.tsx should have no leftover tokens');
        console.log(`✅ scenario 7: bundled nextjs template → ${result.filesCopied} files, substituted=${result.filesSubstituted}`);
    }
    finally {
        rmTmp(target);
    }
}
try {
    scenarioRegistryDiscovers();
    scenarioApplyWithSubstitution();
    scenarioExcludeHonored();
    scenarioEmptyTargetEnforced();
    scenarioDryRun();
    scenarioPostInstallSubstituted();
    scenarioBundledNextjs();
    console.log('TEST_SCAFFOLD_ENGINE_OK');
}
catch (err) {
    console.error('TEST_SCAFFOLD_ENGINE_FAIL', err);
    process.exit(1);
}
