import { mochaLocalRegistry , mochaTmpdir , Package , repoVersions , writePackage , } from "@adpt/testutils" ; import { Constructor , repoDirs , yarn , } from "@adpt/utils" ; import execa from "execa" ; import * as fs from "fs-extra" ; import * as os from "os" ; import * as path from "path" ; import should from "should" ; import { packageDirs } from "../testlib" ; import { callerModule , findMummy , findMummyUrn , mockRegistry_ , MummyJson , MummyRegistry , reanimate , reanimateUrn , registerObject , } from "../../src/reanimate/reanimate" ; import * as firstBaseReg from "./test_baseReg" ; import * as firstInFunc from "./test_inFunc" ; import * as firstLateExport from "./test_lateExport" ; import { isLiving } from "./test_living" ; import * as firstVictim from "./test_victim" ; const currentAdaptVersion = repoVersions . core ; const curMod = { victim : firstVictim , lateExport : firstLateExport , inFunc : firstInFunc , baseReg : firstBaseReg , } ; type Mods = typeof curMod ; type ModName = keyof Mods ; type CtorNames < T > = { [ K in keyof T ] : T [ K ] extends Constructor < any > ? K : never ; } [ keyof T ] ; type ModCtorNames < Mname extends ModName > = CtorNames < Mods [ Mname ] > ; function deleteModule ( modName ) { delete require . cache [ curMod [ modName ] . module . id ] ; } function requireModule ( modName ) { const current = curMod [ modName ] ; curMod [ modName ] = require ( `./test_${ modName }` ) ; should ( curMod [ modName ] ) . not . equal ( current ) ; } describe ( "Reanimate basic tests" , ( ) => { let origRegistry ; let firstMummyJ ; before ( ( ) => { origRegistry = mockRegistry_ ( ) ; } ) ; after ( ( ) => { mockRegistry_ ( origRegistry ) ; } ) ; it ( "Should have registered on first import" , async ( ) => { mockRegistry_ ( origRegistry ) ; firstMummyJ = findMummy ( firstVictim . Victim ) ; should ( firstMummyJ ) . be . type ( "string" ) ; const parsed = JSON . parse ( firstMummyJ ) ; should ( parsed ) . eql ( { name : "Victim" , namespace : "" , pkgName : "@adpt/core" , pkgVersion : currentAdaptVersion , relFilePath : "../test/reanimate/test_victim.js" , } ) ; const obj = await reanimate ( firstMummyJ ) ; should ( obj ) . equal ( firstVictim . Victim ) ; const mummyUrn = findMummyUrn ( firstVictim . Victim ) ; should ( mummyUrn ) . equal ( `urn:Adapt:@adpt/core:${ currentAdaptVersion }::../test/reanimate/test_victim.js:Victim` ) ; const obj2 = await reanimateUrn ( mummyUrn ) ; should ( obj2 ) . equal ( firstVictim . Victim ) ; } ) ; it ( "Should store and reanimate with new module and registry" , async ( ) => { mockRegistry_ ( null ) ; let v = new curMod . victim . Victim ( ) ; should ( isLiving ( v ) ) . be . True ( ) ; registerObject ( curMod . victim . Victim , "Victim" , curMod . victim . module ) ; const mummified = findMummy ( curMod . victim . Victim ) ; should ( mummified ) . be . type ( "string" ) ; const parsed = JSON . parse ( mummified ) ; should ( parsed ) . eql ( { name : "Victim" , namespace : "" , pkgName : "@adpt/core" , pkgVersion : currentAdaptVersion , relFilePath : "../test/reanimate/test_victim.js" , } ) ; mockRegistry_ ( null ) ; deleteModule ( "victim" ) ; const obj = await reanimate ( mummified ) ; should ( obj ) . not . equal ( curMod . victim . Victim ) ; requireModule ( "victim" ) ; should ( obj ) . equal ( curMod . victim . Victim ) ; v = new obj ( ) ; should ( isLiving ( v ) ) . be . True ( ) ; should ( v . constructor . name ) . equal ( "VictimInternal" ) ; } ) ; it ( "Should store and reanimate object registered before export" , async ( ) => { let v = new curMod . lateExport . LateExport ( ) ; should ( isLiving ( v ) ) . be . True ( ) ; should ( v . constructor . name ) . equal ( "LateExportInternal" ) ; deleteModule ( "lateExport" ) ; mockRegistry_ ( null ) ; requireModule ( "lateExport" ) ; const firstMummyLate = findMummy ( curMod . lateExport . LateExport ) ; should ( firstMummyLate ) . be . type ( "string" ) ; const parsed = JSON . parse ( firstMummyLate ) ; should ( parsed ) . eql ( { name : "LateExportReg" , namespace : "$adaptExports" , pkgName : "@adpt/core" , pkgVersion : currentAdaptVersion , relFilePath : "../test/reanimate/test_lateExport.js" , } ) ; const mummyUrn = findMummyUrn ( curMod . lateExport . LateExport ) ; should ( mummyUrn ) . equal ( `urn:Adapt:@adpt/core:${ currentAdaptVersion }:$adaptExports:../test/reanimate/test_lateExport.js:LateExportReg` ) ; const firstObj = await reanimate ( firstMummyLate ) ; v = new firstObj ( ) ; should ( isLiving ( v ) ) . be . True ( ) ; should ( v . constructor . name ) . equal ( "LateExportInternal" ) ; const firstObj2 = await reanimateUrn ( mummyUrn ) ; should ( firstObj2 ) . equal ( firstObj ) ; deleteModule ( "lateExport" ) ; mockRegistry_ ( null ) ; const obj = await reanimate ( firstMummyLate ) ; should ( obj ) . not . equal ( firstObj ) ; should ( obj ) . not . equal ( curMod . lateExport . LateExport ) ; requireModule ( "lateExport" ) ; should ( obj ) . equal ( curMod . lateExport . LateExport ) ; v = new obj ( ) ; should ( isLiving ( v ) ) . be . True ( ) ; should ( v . constructor . name ) . equal ( "LateExportInternal" ) ; } ) ; it ( "callerModule should return valid modules" , ( ) => { const files = [ "dist/src/reanimate/reanimate.js" , "dist/test/reanimate/reanimate.spec.js" , ] ; for ( let i = 0 ; i < files . length ; i ++ ) { const mod = callerModule ( i ) ; should ( mod ) . be . type ( "object" ) ; should ( mod . filename ) . equal ( path . join ( packageDirs . root , files [ i ] ) ) ; if ( i === 1 ) { should ( mod ) . equal ( module ) ; } } } ) ; it ( "Should store and reanimate with module default" , async ( ) => { mockRegistry_ ( null ) ; curMod . inFunc . doRegister ( ) ; const mummy = findMummy ( curMod . inFunc . InFunc ) ; should ( mummy ) . be . type ( "string" ) ; const parsed = JSON . parse ( mummy ) ; should ( parsed ) . eql ( { name : "InFunc" , namespace : "" , pkgName : "@adpt/core" , pkgVersion : currentAdaptVersion , relFilePath : "../test/reanimate/test_inFunc.js" , } ) ; mockRegistry_ ( null ) ; deleteModule ( "inFunc" ) ; const obj = await reanimate ( mummy ) ; should ( obj ) . not . equal ( curMod . inFunc . InFunc ) ; requireModule ( "inFunc" ) ; should ( obj ) . equal ( curMod . inFunc . InFunc ) ; const v = new obj ( ) ; should ( isLiving ( v ) ) . be . True ( ) ; should ( v . constructor . name ) . equal ( "InFuncInternal" ) ; } ) ; } ) ; async function basicTestConstructor < M extends ModName , R extends ModCtorNames < M > > ( modName , regName , newName = regName ) { const getCtor = ( name ) : any => curMod [ modName ] [ name ] ; const regCtor = getCtor ( regName ) ; const newCtor = getCtor ( newName ) ; should ( ( ) => findMummy ( regCtor ) ) . throwError ( /Unable to look up JSON/ ) ; new newCtor ( ) ; const mummyJson = findMummy ( regCtor ) ; should ( mummyJson ) . be . type ( "string" ) ; const mummy = JSON . parse ( mummyJson ) ; should ( mummy ) . eql ( { name : regName , namespace : "" , pkgName : "@adpt/core" , pkgVersion : currentAdaptVersion , relFilePath : `../test/reanimate/test_${ modName }.js` , } ) ; let live = await reanimate ( mummyJson ) ; should ( live ) . equal ( regCtor ) ; mockRegistry_ ( null ) ; deleteModule ( modName ) ; live = await reanimate ( mummyJson ) ; should ( live ) . not . equal ( regCtor ) ; requireModule ( modName ) ; should ( live ) . equal ( getCtor ( regName ) ) ; const b = new live ( ) ; should ( b . constructor . name ) . equal ( regName ) ; } describe ( "registerConstructor" , ( ) => { it ( "Should store and reanimate constructor" , async ( ) => { await basicTestConstructor ( "baseReg" , "BaseReg" ) ; } ) ; it ( "Should store and reanimate indirect register" , async ( ) => { await basicTestConstructor ( "baseReg" , "BaseRegFunc" ) ; } ) ; it ( "Should store and reanimate nested constructor" , async ( ) => { await basicTestConstructor ( "baseReg" , "BaseReg" , "BaseRegNested" as "BaseReg" ) ; } ) ; it ( "Should store and reanimate derived class" , async ( ) => { await basicTestConstructor ( "baseReg" , "Derived" ) ; } ) ; } ) ; const mainIndexJs = `
const re = require("@usys/reanimate");

try {
    if (process.argv.length !== 3) {
        throw new Error("Usage: node index.js <MummyJson>|show1|show2|showlate");
    }

    const mummy = process.argv[2];

    if (mummy === "show1") {
        const o = require("@usys/oldlib");
        console.log(re.findMummy(o.Victim));
        process.exit(0);
    }
    if (mummy === "show2") {
        const v = require("@usys/victim");
        console.log(re.findMummy(v.Victim));
        process.exit(0);
    }
    if (mummy === "showlate") {
        const v = require("@usys/register-in-func");
        // registerObject is only called on construction
        new v.LateVictim(true);
        console.log(re.findMummy(v.LateVictim));
        process.exit(0);
    }

    re.reanimate(mummy)
    .then((alive) => {
        new alive();
        console.log("SUCCESS");
    })
    .catch((err) => {
        console.log("FAILED\\n", err);
        process.exit(1);
    });

} catch (err) {
    console.log("FAILED\\n", err);
    process.exit(1);
}
` ; const mainPackage = { pkgJson : { name : "@usys/test_proj" , dependencies : { "@usys/reanimate" : "1.0.0" , "@usys/victim" : "2.0.0" , "@usys/oldlib" : "1.0.0" , "@usys/register-in-func" : "1.0.0" , } } , files : { "index.js" : mainIndexJs , } , } ; function victimJs ( version ) { return `
const re = require("@usys/reanimate");

class Victim {
    constructor() {
        console.log("Created Victim version ${ version }");
    }
}
exports.Victim = Victim;

re.registerObject(Victim, "Victim", module);
` ; } function victimPackage ( version ) { return { pkgJson : { name : "@usys/victim" , version , dependencies : { "@usys/reanimate" : "1.0.0" } } , files : { "index.js" : victimJs ( version ) } } ; } const registerInFunc = `
const re = require("@usys/reanimate");

// registerObject NOT called from module scope

class LateVictimInternal {
    constructor(noPrint) {
        re.registerObject(LateVictimInternal, "Avictim", module);
        if (!noPrint) console.log("Created LateVictim");
    }
}
exports.LateVictim = LateVictimInternal;
` ; const registerInFuncPackage = { pkgJson : { name : "@usys/register-in-func" , dependencies : { "@usys/reanimate" : "1.0.0" } } , files : { "index.js" : registerInFunc } } ; const distSrc = path . join ( packageDirs . dist , "src" ) ; const reanimateIndexJs = `
const path = require("path");

global.getAdaptContext = () => ({
    projectRoot: path.resolve("."),
});

module.exports = require('./reanimate');
` ; const reanimatePackage = { pkgJson : { name : "@usys/reanimate" , dependencies : { "@adpt/utils" : repoVersions . utils , "callsites" : "^3.1.0" , "json-stable-stringify" : "1.0.1" , "read-pkg-up" : "^7.0.1" , "ts-custom-error" : "^3.2.0" , "urn-lib" : "^1.2.0" , } , } , files : { "index.js" : reanimateIndexJs , } , copy : { "reanimate/index.js" : path . join ( distSrc , "reanimate" , "reanimate.js" ) , "error.js" : path . join ( distSrc , "error.js" ) , "packageinfo.js" : path . join ( distSrc , "packageinfo.js" ) , } } ; const oldlibIndexJs = `
const v = require("@usys/victim");
exports.Victim = v.Victim;
` ; const oldlibPackage = { pkgJson : { name : "@usys/oldlib" , dependencies : { "@usys/victim" : "1.0.0" , } , } , files : { "index.js" : oldlibIndexJs , } } ; async function createProject ( ) { await writePackage ( "." , mainPackage ) ; await writePackage ( "reanimate" , reanimatePackage ) ; await writePackage ( "oldlib" , oldlibPackage ) ; await writePackage ( "victim1" , victimPackage ( "1.0.0" ) ) ; await writePackage ( "victim2" , victimPackage ( "2.0.0" ) ) ; await writePackage ( "register-in-func" , registerInFuncPackage ) ; } async function showMummy ( which ) { const res = await execa ( "node" , [ "index.js" , "show" + which ] ) ; const mummyJson = res . stdout ; should ( mummyJson ) . not . match ( /FAILED/ ) ; const mummy = JSON . parse ( mummyJson ) ; should ( mummy ) . be . type ( "object" ) ; should ( mummy . relFilePath ) . equal ( "index.js" ) ; return mummyJson ; } function checkMummyVictim ( which , mummyJson ) { const mummy = JSON . parse ( mummyJson ) ; should ( mummy ) . be . type ( "object" ) ; should ( mummy . name ) . equal ( "Victim" ) ; should ( mummy . pkgName ) . equal ( "@usys/victim" ) ; should ( mummy . pkgVersion ) . equal ( which + ".0.0" ) ; } describe ( "Reanimate in package tests" , function ( ) { let mummy1 ; let mummy2 ; this . timeout ( 10 * 1000 ) ; mochaTmpdir . all ( "adapt-reanimate-test" ) ; before ( createProject ) ; const localRegistry = mochaLocalRegistry . all ( { publishList : [ repoDirs . utils , "reanimate" , "oldlib" , "victim1" , "victim2" , "register-in-func" , ] } ) ; before ( async function ( ) { this . timeout ( 20 * 1000 ) ; await yarn . install ( localRegistry . yarnProxyOpts ) ; } ) ; it ( "Should reanimate top level dependency from mummy" , async ( ) => { const mummyJson = await showMummy ( "2" ) ; checkMummyVictim ( "2" , mummyJson ) ; mummy2 = mummyJson ; const res = await execa ( "node" , [ "index.js" , mummyJson ] ) ; should ( res . stdout ) . match ( /SUCCESS/ ) ; should ( res . stdout ) . match ( /Created Victim version 2.0.0/ ) ; } ) ; it ( "Should reanimate sub dependency from mummy" , async ( ) => { const mummyJson = await showMummy ( "1" ) ; checkMummyVictim ( "1" , mummyJson ) ; mummy1 = mummyJson ; const res = await execa ( "node" , [ "index.js" , mummyJson ] ) ; should ( res . stdout ) . match ( /SUCCESS/ ) ; should ( res . stdout ) . match ( /Created Victim version 1.0.0/ ) ; } ) ; it ( "Should reanimate non-module-level registerObject" , async ( ) => { const mummyJson = await showMummy ( "late" ) ; const mummy = JSON . parse ( mummyJson ) ; should ( mummy . name ) . equal ( "LateVictim" ) ; should ( mummy . pkgName ) . equal ( "@usys/register-in-func" ) ; should ( mummy . pkgVersion ) . equal ( "1.0.0" ) ; const res = await execa ( "node" , [ "index.js" , mummyJson ] ) ; should ( res . stdout ) . match ( /SUCCESS/ ) ; should ( res . stdout ) . match ( /Created LateVictim/ ) ; } ) ; it ( "Should reanimate with different root dir" , async ( ) => { if ( ! mummy1 || ! mummy2 ) { throw new Error ( `Previous tests did not run successfully` ) ; } const oldTmp = process . cwd ( ) ; const newTmp = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "adapt-reanimate" ) ) ; try { process . chdir ( newTmp ) ; await createProject ( ) ; await yarn . install ( localRegistry . yarnProxyOpts ) ; let res = await execa ( "node" , [ "index.js" , mummy1 ] ) ; should ( res . stdout ) . match ( /SUCCESS/ ) ; should ( res . stdout ) . match ( /Created Victim version 1.0.0/ ) ; res = await execa ( "node" , [ "index.js" , mummy2 ] ) ; should ( res . stdout ) . match ( /SUCCESS/ ) ; should ( res . stdout ) . match ( /Created Victim version 2.0.0/ ) ; } finally { process . chdir ( oldTmp ) ; await fs . remove ( newTmp ) ; } } ) ; } ) ;