import type { CompilerFileWatcherCallback , CompilerFsStats , CompilerSystem , CompilerSystemCreateDirectoryOptions , CompilerSystemCreateDirectoryResults , CompilerSystemRealpathResults , CompilerSystemRemoveDirectoryOptions , CompilerSystemRemoveDirectoryResults , CompilerSystemRemoveFileResults , CompilerSystemRenameResults , CompilerSystemWriteFileResults , CopyResults , CopyTask , Logger , } from '../../declarations' ; import platformPath from 'path-browserify' ; import { basename , dirname , join } from 'path' ; import * as process from 'process' ; import * as os from 'os' ; import { buildEvents } from '../events' ; import { createLogger } from './logger/console-logger' ; import { createWebWorkerMainController } from './worker/web-worker-main' ; import { HAS_WEB_WORKER , IS_BROWSER_ENV , IS_WEB_WORKER_ENV } from './environment' ; import { isRootPath , normalizePath } from '@utils' ; import { resolveModuleIdAsync } from './resolve/resolve-module-async' ; import { version } from '../../version' ; export const createSystem = ( c ? ) => { const logger = c && c . logger ? c . logger : createLogger ( ) ; const items = new Map < string , FsItem > ( ) ; const destroys = new Set < ( ) => Promise < void > | void > ( ) ; const addDestory = ( cb ) => destroys . add ( cb ) ; const removeDestory = ( cb ) => destroys . delete ( cb ) ; const events = buildEvents ( ) ; const hardwareConcurrency = ( IS_BROWSER_ENV && navigator . hardwareConcurrency ) || 1 ; const destroy = async ( ) => { const waits = [ ] ; destroys . forEach ( ( cb ) => { try { const rtn = cb ( ) ; if ( rtn && rtn . then ) { waits . push ( rtn ) ; } } catch ( e ) { logger . error ( `stencil sys destroy: ${ e }` ) ; } } ) ; await Promise . all ( waits ) ; destroys . clear ( ) ; } ; const normalize = ( p ) => { if ( p === '/' || p === '' ) { return '/' ; } const dir = dirname ( p ) ; const base = basename ( p ) ; if ( dir . endsWith ( '/' ) ) { return normalizePath ( `${ dir }${ base }` ) ; } return normalizePath ( `${ dir }/${ base }` ) ; } ; const accessSync = ( p ) => { const item = items . get ( normalize ( p ) ) ; return ! ! ( item && ( item . isDirectory || ( item . isFile && typeof item . data === 'string' ) ) ) ; } ; const access = async ( p ) => accessSync ( p ) ; const copyFile = async ( src , dest ) => { writeFileSync ( dest , readFileSync ( src ) ) ; return true ; } ; const isTTY = ( ) : boolean => { return ! ! process ?. stdout ?. isTTY ; } ; const homeDir = ( ) => { return os . homedir ( ) ; } ; const createDirSync = ( p , opts ? ) => { p = normalize ( p ) ; const results = { basename : basename ( p ) , dirname : dirname ( p ) , path : p , newDirs : [ ] , error : null , } ; createDirRecursiveSync ( p , opts , results ) ; return results ; } ; const createDirRecursiveSync = ( p , opts , results ) => { const parentDir = dirname ( p ) ; if ( opts && opts . recursive && ! isRootPath ( parentDir ) ) { createDirRecursiveSync ( parentDir , opts , results ) ; } const item = items . get ( p ) ; if ( ! item ) { items . set ( p , { basename : basename ( p ) , dirname : parentDir , isDirectory : true , isFile : false , watcherCallbacks : null , data : undefined , } ) ; results . newDirs . push ( p ) ; emitDirectoryWatch ( p , new Set ( ) ) ; } else { item . isDirectory = true ; item . isFile = false ; } } ; const createDir = async ( p , opts ? ) => createDirSync ( p , opts ) ; const encodeToBase64 = ( str ) => btoa ( unescape ( encodeURIComponent ( str ) ) ) ; const getCurrentDirectory = ( ) => '/' ; const getCompilerExecutingPath = ( ) => { if ( IS_WEB_WORKER_ENV ) { return location . href ; } return sys . getRemoteModuleUrl ( { moduleId : '@stencil/core' , path : 'compiler/stencil.min.js' } ) ; } ; const isSymbolicLink = async ( _p ) => false ; const readDirSync = ( p ) => { p = normalize ( p ) ; const dirItems = [ ] ; const dir = items . get ( p ) ; if ( dir && dir . isDirectory ) { items . forEach ( ( item , itemPath ) => { if ( itemPath !== '/' && ( item . isDirectory || ( item . isFile && typeof item . data === 'string' ) ) ) { if ( p . endsWith ( '/' ) && `${ p }${ item . basename }` === itemPath ) { dirItems . push ( itemPath ) ; } else if ( `${ p }/${ item . basename }` === itemPath ) { dirItems . push ( itemPath ) ; } } } ) ; } return dirItems . sort ( ) ; } ; const readDir = async ( p ) => readDirSync ( p ) ; const readFileSync = ( p ) => { p = normalize ( p ) ; const item = items . get ( p ) ; if ( item && item . isFile ) { return item . data ; } return undefined ; } ; const readFile = async ( p ) => readFileSync ( p ) ; const realpathSync = ( p ) => { const results = { path : normalize ( p ) , error : null , } ; return results ; } ; const realpath = async ( p ) => realpathSync ( p ) ; const rename = async ( oldPath , newPath ) => { oldPath = normalizePath ( oldPath ) ; newPath = normalizePath ( newPath ) ; const results = { oldPath , newPath , renamed : [ ] , oldDirs : [ ] , oldFiles : [ ] , newDirs : [ ] , newFiles : [ ] , isFile : false , isDirectory : false , error : null , } ; const stats = statSync ( oldPath ) ; if ( ! stats . error ) { if ( stats . isFile ) { results . isFile = true ; } else if ( stats . isDirectory ) { results . isDirectory = true ; } renameNewRecursiveSync ( oldPath , newPath , results ) ; if ( ! results . error ) { if ( results . isDirectory ) { const rmdirResults = removeDirSync ( oldPath , { recursive : true } ) ; if ( rmdirResults . error ) { results . error = rmdirResults . error ; } else { results . oldDirs . push ( ... rmdirResults . removedDirs ) ; results . oldFiles . push ( ... rmdirResults . removedFiles ) ; } } else if ( results . isFile ) { const removeFileResults = removeFileSync ( oldPath ) ; if ( removeFileResults . error ) { results . error = removeFileResults . error ; } else { results . oldFiles . push ( oldPath ) ; } } } } else { results . error = `${ oldPath } does not exist` ; } return results ; } ; const renameNewRecursiveSync = ( oldPath , newPath , results ) => { const itemStat = statSync ( oldPath ) ; if ( ! itemStat . error && ! results . error ) { if ( itemStat . isFile ) { const newFileParentDir = dirname ( newPath ) ; const createDirResults = createDirSync ( newFileParentDir , { recursive : true } ) ; const fileContent = items . get ( oldPath ) . data ; const writeResults = writeFileSync ( newPath , fileContent ) ; results . newDirs . push ( ... createDirResults . newDirs ) ; results . renamed . push ( { oldPath , newPath , isDirectory : false , isFile : true , } ) ; if ( writeResults . error ) { results . error = writeResults . error ; } else { results . newFiles . push ( newPath ) ; } } else if ( itemStat . isDirectory ) { const oldDirItemChildPaths = readDirSync ( oldPath ) ; const createDirResults = createDirSync ( newPath , { recursive : true } ) ; results . newDirs . push ( ... createDirResults . newDirs ) ; results . renamed . push ( { oldPath , newPath , isDirectory : true , isFile : false , } ) ; for ( const oldDirItemChildPath of oldDirItemChildPaths ) { const newDirItemChildPath = oldDirItemChildPath . replace ( oldPath , newPath ) ; renameNewRecursiveSync ( oldDirItemChildPath , newDirItemChildPath , results ) ; } } } } ; const resolvePath = ( p ) => normalize ( p ) ; const removeDirSync = ( p , opts = { } ) => { const results = { basename : basename ( p ) , dirname : dirname ( p ) , path : p , removedDirs : [ ] , removedFiles : [ ] , error : null , } ; remoreDirSyncRecursive ( p , opts , results ) ; return results ; } ; const remoreDirSyncRecursive = ( p , opts , results ) => { if ( ! results . error ) { p = normalize ( p ) ; const dirItemPaths = readDirSync ( p ) ; if ( opts && opts . recursive ) { for ( const dirItemPath of dirItemPaths ) { const item = items . get ( dirItemPath ) ; if ( item ) { if ( item . isDirectory ) { remoreDirSyncRecursive ( dirItemPath , opts , results ) ; } else if ( item . isFile ) { const removeFileResults = removeFileSync ( dirItemPath ) ; if ( removeFileResults . error ) { results . error = removeFileResults . error ; } else { results . removedFiles . push ( dirItemPath ) ; } } } } } else { if ( dirItemPaths . length > 0 ) { results . error = `cannot delete directory that contains files/subdirectories` ; return ; } } items . delete ( p ) ; emitDirectoryWatch ( p , new Set ( ) ) ; results . removedDirs . push ( p ) ; } } ; const removeDir = async ( p , opts = { } ) => removeDirSync ( p , opts ) ; const statSync = ( p ) : CompilerFsStats => { p = normalize ( p ) ; const item = items . get ( p ) ; if ( item && ( item . isDirectory || ( item . isFile && typeof item . data === 'string' ) ) ) { return { isDirectory : item . isDirectory , isFile : item . isFile , isSymbolicLink : false , size : item . isFile && item . data ? item . data . length : 0 , error : null , } ; } return { isDirectory : false , isFile : false , isSymbolicLink : false , size : 0 , error : `ENOENT: no such file or directory, statSync '${ p }'` , } ; } ; const stat = async ( p ) => statSync ( p ) ; const removeFileSync = ( p ) => { p = normalize ( p ) ; const results = { basename : basename ( p ) , dirname : dirname ( p ) , path : p , error : null , } ; const item = items . get ( p ) ; if ( item ) { if ( item . watcherCallbacks ) { for ( const watcherCallback of item . watcherCallbacks ) { watcherCallback ( p , 'fileDelete' ) ; } } items . delete ( p ) ; emitDirectoryWatch ( p , new Set ( ) ) ; } return results ; } ; const removeFile = async ( p ) => removeFileSync ( p ) ; const watchDirectory = ( p , dirWatcherCallback ) => { p = normalize ( p ) ; const item = items . get ( p ) ; const close = ( ) => { const closeItem = items . get ( p ) ; if ( closeItem && closeItem . watcherCallbacks ) { const index = closeItem . watcherCallbacks . indexOf ( dirWatcherCallback ) ; if ( index > - 1 ) { closeItem . watcherCallbacks . splice ( index , 1 ) ; } } } ; addDestory ( close ) ; if ( item ) { item . isDirectory = true ; item . isFile = false ; item . watcherCallbacks = item . watcherCallbacks || [ ] ; item . watcherCallbacks . push ( dirWatcherCallback ) ; } else { items . set ( p , { basename : basename ( p ) , dirname : dirname ( p ) , isDirectory : true , isFile : false , watcherCallbacks : [ dirWatcherCallback ] , data : undefined , } ) ; } return { close ( ) { removeDestory ( close ) ; close ( ) ; } , } ; } ; const watchFile = ( p , fileWatcherCallback ) => { p = normalize ( p ) ; const item = items . get ( p ) ; const close = ( ) => { const closeItem = items . get ( p ) ; if ( closeItem && closeItem . watcherCallbacks ) { const index = closeItem . watcherCallbacks . indexOf ( fileWatcherCallback ) ; if ( index > - 1 ) { closeItem . watcherCallbacks . splice ( index , 1 ) ; } } } ; addDestory ( close ) ; if ( item ) { item . isDirectory = false ; item . isFile = true ; item . watcherCallbacks = item . watcherCallbacks || [ ] ; item . watcherCallbacks . push ( fileWatcherCallback ) ; } else { items . set ( p , { basename : basename ( p ) , dirname : dirname ( p ) , isDirectory : false , isFile : true , watcherCallbacks : [ fileWatcherCallback ] , data : undefined , } ) ; } return { close ( ) { removeDestory ( close ) ; close ( ) ; } , } ; } ; const emitDirectoryWatch = ( p , emitted ) => { const parentDir = normalize ( dirname ( p ) ) ; const dirItem = items . get ( parentDir ) ; if ( dirItem && dirItem . isDirectory && dirItem . watcherCallbacks ) { for ( const watcherCallback of dirItem . watcherCallbacks ) { watcherCallback ( p , null ) ; } } if ( ! emitted . has ( parentDir ) ) { emitted . add ( parentDir ) ; emitDirectoryWatch ( parentDir , emitted ) ; } } ; const writeFileSync = ( p , data ) => { p = normalize ( p ) ; const results = { path : p , error : null , } ; const item = items . get ( p ) ; if ( item ) { const hasChanged = item . data !== data ; item . data = data ; if ( hasChanged && item . watcherCallbacks ) { for ( const watcherCallback of item . watcherCallbacks ) { watcherCallback ( p , 'fileUpdate' ) ; } } } else { items . set ( p , { basename : basename ( p ) , dirname : dirname ( p ) , isDirectory : false , isFile : true , watcherCallbacks : null , data , } ) ; emitDirectoryWatch ( p , new Set ( ) ) ; } return results ; } ; /**
     * `self` is the global namespace object used within a web worker.
     * `window` is the browser's global namespace object (I reorganized this to check the reference on that second)
     * `global` is Node's global namespace object. https://nodejs.org/api/globals.html#globals_global
     *
     * loading in this order should allow workers, which are most common, then browser,
     * then Node to grab the reference to fetch correctly.
     */ const fetch = typeof self !== 'undefined' ? self ?. fetch : typeof window !== 'undefined' ? window ?. fetch : typeof global !== 'undefined' ? global ?. fetch : undefined ; const writeFile = async ( p , data ) => writeFileSync ( p , data ) ; const tmpDirSync = ( ) => '/.tmp' ; const tick = Promise . resolve ( ) ; const nextTick = ( cb ) => tick . then ( cb ) ; const generateContentHash = async ( content , hashLength ) => { const arrayBuffer = await crypto . subtle . digest ( 'SHA-256' , new TextEncoder ( ) . encode ( content ) ) ; const hashArray = Array . from ( new Uint8Array ( arrayBuffer ) ) ; let hashHex = hashArray . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ; if ( typeof hashLength === 'number' ) { hashHex = hashHex . substr ( 0 , hashLength ) ; } return hashHex ; } ; const copy = async ( copyTasks , srcDir ) => { const results = { diagnostics : [ ] , dirPaths : [ ] , filePaths : [ ] , } ; logger . info ( 'todo, copy task' , copyTasks . length , srcDir ) ; return results ; } ; const getEnvironmentVar = ( key ) => { return process ?. env [ key ] ; } ; const getLocalModulePath = ( opts ) => join ( opts . rootDir , 'node_modules' , opts . moduleId , opts . path ) ; const getRemoteModuleUrl = ( opts ) => { const npmBaseUrl = 'https://cdn.jsdelivr.net/npm/' ; const path = `${ opts . moduleId }${ opts . version ? '@' + opts . version : '' }/${ opts . path }` ; return new URL ( path , npmBaseUrl ) . href ; } ; const fileWatchTimeout = 32 ; createDirSync ( '/' ) ; const sys = { name : 'in-memory' , version , events , access , accessSync , addDestory , copyFile , createDir , createDirSync , homeDir , isTTY , getEnvironmentVar , destroy , encodeToBase64 , exit : async ( exitCode ) => logger . warn ( `exit ${ exitCode }` ) , getCurrentDirectory , getCompilerExecutingPath , getLocalModulePath , getRemoteModuleUrl , hardwareConcurrency , isSymbolicLink , nextTick , normalizePath : normalize , platformPath , readDir , readDirSync , readFile , readFileSync , realpath , realpathSync , removeDestory , rename , fetch , resolvePath , removeDir , removeDirSync , stat , statSync , tmpDirSync , removeFile , removeFileSync , watchDirectory , watchFile , watchTimeout : fileWatchTimeout , writeFile , writeFileSync , generateContentHash , createWorkerController : HAS_WEB_WORKER ? ( maxConcurrentWorkers ) => createWebWorkerMainController ( sys , maxConcurrentWorkers ) : null , details : { cpuModel : '' , freemem : ( ) => 0 , platform : '' , release : '' , totalmem : 0 , } , copy , } ; sys . resolveModuleId = ( opts ) => resolveModuleIdAsync ( sys , null , opts ) ; return sys ; } ; interface FsItem { data : string ; basename : string ; dirname : string ; isFile : boolean ; isDirectory : boolean ; watcherCallbacks : CompilerFileWatcherCallback [ ] ; }