import os from 'os' ; import path from 'path' ; import pc from 'picocolors' ; import { randomBytes } from '@stablelib/random' ; import * as u8a from 'uint8arrays' ; import { promises as fs } from 'fs' ; import { Ed25519Provider } from 'key-did-provider-ed25519' ; import CeramicClient from '@ceramicnetwork/http-client' ; import { CeramicApi , LogLevel , Networks , StreamUtils , SyncOptions } from '@ceramicnetwork/common' ; import StreamID , { CommitID } from '@ceramicnetwork/streamid' ; import { CeramicDaemon } from './ceramic-daemon' ; import { DaemonConfig , IpfsMode , StateStoreMode } from './daemon-config' ; import { TileDocument , TileMetadataArgs } from '@ceramicnetwork/stream-tile' ; import ThreeIdResolver from '@ceramicnetwork/3id-did-resolver' ; import KeyDidResolver from 'key-did-resolver' ; import { Resolver } from 'did-resolver' ; import { DID } from 'dids' ; import { DEFAULT_STATE_STORE_DIRECTORY } from '@ceramicnetwork/core/lib/store/pin-store-factory' ; const DEFAULT_DAEMON_CONFIG_FILENAME = 'daemon.config.json' ; const DEFAULT_CLI_CONFIG_FILENAME = 'client.config.json' ; const LEGACY_CLI_CONFIG_FILENAME = 'config.json' ; const DEFAULT_CONFIG_PATH = path . join ( os . homedir ( ) , '.ceramic' ) ; const DEFAULT_DAEMON_CONFIG = DaemonConfig . fromObject ( { anchor : { } , 'http-api' : { 'cors-allowed-origins' : [ new RegExp ( '.*' ) ] } , ipfs : { mode : IpfsMode . BUNDLED } , logger : { 'log-level' : LogLevel . important , 'log-to-files' : false } , network : { name : Networks . TESTNET_CLAY } , node : { } , 'state-store' : { mode : StateStoreMode . FS , 'local-directory' : DEFAULT_STATE_STORE_DIRECTORY , } , } ) ; /**
 * CLI configuration
 */ interface CliConfig { seed ? : string ; ceramicHost ? : string ; [ index ] : any ; } /**
 * Ceramic CLI utility functions
 */ export class CeramicCliUtils { param configFilePath param ipfsApi param ethereumRpc param anchorServiceApi param ipfsPinningEndpoints param stateStoreDirectory param stateStoreS3Bucket param gateway param port param hostname param debug param verbose param logToFiles param logDirectory param network param pubsubTopic param corsAllowedOrigins param syncOverride static async createDaemon ( configFilePath , ipfsApi , ethereumRpc , anchorServiceApi , ipfsPinningEndpoints , stateStoreDirectory , stateStoreS3Bucket , gateway , port , hostname , debug , verbose , logToFiles , logDirectory , network , pubsubTopic , corsAllowedOrigins , syncOverride ) { const config = await this . _loadDaemonConfig ( ) ; { if ( stateStoreDirectory && stateStoreS3Bucket ) { throw new Error ( 'Cannot specify both --state-store-directory and --state-store-s3-bucket. Only one state store - either on local storage or on S3 - can be used at a time' ) ; } if ( anchorServiceApi ) { config . anchor . anchorServiceUrl = anchorServiceApi ; } if ( ethereumRpc ) { config . anchor . ethereumRpcUrl = ethereumRpc ; } if ( corsAllowedOrigins ) { config . httpApi . corsAllowedOrigins = corsAllowedOrigins . split ( ' ' ) . map ( ( origin ) => new RegExp ( origin ) ) ; } if ( hostname ) { config . httpApi . hostname = hostname ; } if ( port ) { config . httpApi . port = port ; } if ( ipfsApi ) { config . ipfs . mode = IpfsMode . REMOTE ; config . ipfs . host = ipfsApi ; } if ( ipfsPinningEndpoints ) { config . ipfs . pinningEndpoints = ipfsPinningEndpoints ; } if ( verbose || debug ) { const logLevel = verbose ? LogLevel . verbose : debug ? LogLevel . debug : LogLevel . important ; config . logger . logLevel = logLevel ; } if ( logDirectory ) { config . logger . logDirectory = logDirectory ; } if ( logToFiles ) { config . logger . logToFiles = logToFiles ; } if ( network ) { config . network . name = network ; } if ( pubsubTopic ) { config . network . pubsubTopic = pubsubTopic ; } if ( gateway ) { config . node . gateway = gateway ; } if ( syncOverride ) { config . node . syncOverride = syncOverride ; } if ( stateStoreDirectory ) { config . stateStore . mode = StateStoreMode . FS ; config . stateStore . localDirectory = stateStoreDirectory ; } if ( stateStoreS3Bucket ) { config . stateStore . mode = StateStoreMode . S3 ; config . stateStore . s3Bucket = stateStoreS3Bucket ; } } return CeramicDaemon . create ( config ) ; } param content param controllers param onlyGenesis param deterministic param schemaStreamId static async _createDoc ( content , controllers , onlyGenesis , deterministic , schemaStreamId = null ) { await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const parsedControllers = CeramicCliUtils . _parseControllers ( controllers ) ; const parsedContent = CeramicCliUtils . _parseContent ( content ) ; const metadata = { controllers : parsedControllers , schema : schemaStreamId , deterministic } ; const doc = await TileDocument . create ( ceramic , parsedContent , metadata , { anchor : ! onlyGenesis , publish : ! onlyGenesis , } ) ; console . log ( doc . id ) ; console . log ( JSON . stringify ( doc . content , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param streamId param content param controllers param schemaCommitId static async update ( streamId , content , controllers , schemaCommitId ? ) { const id = StreamID . fromString ( streamId ) ; if ( id . type != TileDocument . STREAM_TYPE_ID ) { throw new Error ( `CLI does not currently support updating stream types other than 'tile'. StreamID ${ id . toString ( ) } has streamtype '${ id . typeName }'` ) ; } await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const parsedControllers = CeramicCliUtils . _parseControllers ( controllers ) ; const parsedContent = CeramicCliUtils . _parseContent ( content ) ; const doc = await TileDocument . load ( ceramic , id ) ; const metadata = { controllers : parsedControllers } ; if ( schemaCommitId ) { const schemaId = CommitID . fromString ( schemaCommitId ) ; metadata . schema = schemaId ; } await doc . update ( parsedContent , metadata ) ; console . log ( JSON . stringify ( doc . content , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param streamRef static async show ( streamRef ) { await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const stream = await TileDocument . load ( ceramic , streamRef ) ; console . log ( JSON . stringify ( stream . content , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param streamRef static async state ( streamRef ) { await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const stream = await ceramic . loadStream ( streamRef ) ; console . log ( JSON . stringify ( StreamUtils . serializeState ( stream . state ) , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param streamId static async watch ( streamId ) { const id = StreamID . fromString ( streamId ) ; await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const doc = await TileDocument . load ( ceramic , id ) ; console . log ( JSON . stringify ( doc . content , null , 2 ) ) ; depreciationNotice ( ) ; doc . subscribe ( ( ) => { console . log ( '--- document changed ---' ) ; console . log ( JSON . stringify ( doc . content , null , 2 ) ) ; } ) ; } ) ; } param streamId static async commits ( streamId ) { const id = StreamID . fromString ( streamId ) ; await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const stream = await ceramic . loadStream ( id ) ; const commits = stream . allCommitIds . map ( ( v ) => v . toString ( ) ) ; console . log ( JSON . stringify ( commits , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param content param controllers param onlyGenesis param deterministic param schemaStreamId static async nonSchemaCreateDoc ( content , controllers , onlyGenesis , deterministic , schemaStreamId = null ) { return CeramicCliUtils . _createDoc ( content , controllers , onlyGenesis , deterministic , schemaStreamId ) ; } param content param controllers param onlyGenesis param deterministic static async schemaCreateDoc ( content , controllers , onlyGenesis , deterministic ) { return CeramicCliUtils . _createDoc ( content , controllers , onlyGenesis , deterministic ) ; } param schemaStreamId param content param controllers static async schemaUpdateDoc ( schemaStreamId , content , controllers ) { StreamID . fromString ( schemaStreamId ) ; return CeramicCliUtils . update ( schemaStreamId , content , controllers , null ) ; } param streamId static async pinAdd ( streamId ) { const id = StreamID . fromString ( streamId ) ; await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const result = await ceramic . pin . add ( id ) ; console . log ( JSON . stringify ( result , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param streamId static async pinRm ( streamId ) { const id = StreamID . fromString ( streamId ) ; await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const result = await ceramic . pin . rm ( id ) ; console . log ( JSON . stringify ( result , null , 2 ) ) ; depreciationNotice ( ) ; } ) ; } param streamId static async pinLs ( streamId ? ) { const id = streamId ? StreamID . fromString ( streamId ) : null ; await CeramicCliUtils . _runWithCeramicClient ( async ( ceramic ) => { const pinnedStreamIds = [ ] ; const iterator = await ceramic . pin . ls ( id ) ; for await ( const id of iterator ) { pinnedStreamIds . push ( id ) ; } console . log ( JSON . stringify ( pinnedStreamIds , null , 2 ) ) ; } ) ; } param seed param ceramic static _makeDID ( seed , ceramic ) { const provider = new Ed25519Provider ( seed ) ; const keyDidResolver = KeyDidResolver . getResolver ( ) ; const threeIdResolver = ThreeIdResolver . getResolver ( ceramic ) ; const resolver = new Resolver ( { ... threeIdResolver , ... keyDidResolver , } ) ; return new DID ( { provider , resolver } ) ; } param fn private static async _runWithCeramicClient ( fn ) { const cliConfig = await CeramicCliUtils . _loadCliConfig ( ) ; if ( ! cliConfig . seed ) { cliConfig . seed = u8a . toString ( randomBytes ( 32 ) , 'base16' ) ; console . log ( 'Identity wallet seed generated' ) ; await CeramicCliUtils . _saveConfig ( cliConfig , DEFAULT_CLI_CONFIG_FILENAME ) ; } let ceramic ; const { ceramicHost } = cliConfig ; if ( ceramicHost !== undefined ) { ceramic = new CeramicClient ( ceramicHost ) ; } else { ceramic = new CeramicClient ( ) ; } const seed = u8a . fromString ( cliConfig . seed , 'base16' ) ; await ceramic . setDID ( CeramicCliUtils . _makeDID ( seed , ceramic ) ) ; try { await fn ( ceramic ) ; } catch ( e ) { console . error ( e . message ) ; process . exit ( - 1 ) ; } finally { ceramic . close ( ) ; } } /**
     * Show config used by CLI client.
     */ static async showCliConfig ( ) { const cliConfig = await this . _loadCliConfig ( ) ; console . log ( JSON . stringify ( cliConfig , null , 2 ) ) ; depreciationNotice ( ) ; } param variable param value static async setCliConfig ( variable , value ) { let cliConfig = await this . _loadCliConfig ( ) ; if ( cliConfig == null ) { cliConfig = { } ; } Object . assign ( cliConfig , { [ variable ] : value , } ) ; await this . _saveConfig ( cliConfig , DEFAULT_CLI_CONFIG_FILENAME ) ; console . log ( `Ceramic CLI configuration ${ variable } set to ${ value }` ) ; console . log ( JSON . stringify ( cliConfig ) ) ; depreciationNotice ( ) ; } param variable static async unsetCliConfig ( variable ) { const cliConfig = await this . _loadCliConfig ( ) ; delete cliConfig [ variable ] ; await this . _saveConfig ( cliConfig , DEFAULT_CLI_CONFIG_FILENAME ) ; console . log ( `Ceramic CLI configuration ${ variable } unset` ) ; console . log ( JSON . stringify ( cliConfig , null , 2 ) ) ; depreciationNotice ( ) ; } private private static async _loadCliConfig ( ) { const configFileContents = await CeramicCliUtils . _loadCliConfigFileContents ( ) ; if ( configFileContents == '' ) { await this . _saveConfig ( { } , DEFAULT_CLI_CONFIG_FILENAME ) ; return { } ; } return JSON . parse ( configFileContents ) ; } private private static async _loadCliConfigFileContents ( ) { const fullCliConfigPath = path . join ( DEFAULT_CONFIG_PATH , DEFAULT_CLI_CONFIG_FILENAME ) ; try { await fs . access ( fullCliConfigPath ) ; return await fs . readFile ( fullCliConfigPath , { encoding : 'utf8' } ) ; } catch ( e ) { } const legacyCliConfigPath = path . join ( DEFAULT_CONFIG_PATH , LEGACY_CLI_CONFIG_FILENAME ) ; try { await fs . access ( legacyCliConfigPath ) ; const fileContents = await fs . readFile ( legacyCliConfigPath , { encoding : 'utf8' } ) ; console . warn ( `Legacy client config file detected at '${ legacyCliConfigPath }', renaming to ${ fullCliConfigPath }` ) ; try { await fs . rename ( legacyCliConfigPath , fullCliConfigPath ) ; } catch ( err ) { console . error ( `Rename failed: ${ err }` ) ; throw err ; } return fileContents ; } catch ( e ) { } return '' ; } private static async _loadDaemonConfig ( ) { const fullDaemonConfigPath = path . join ( DEFAULT_CONFIG_PATH , DEFAULT_DAEMON_CONFIG_FILENAME ) ; try { await fs . access ( fullDaemonConfigPath ) ; } catch ( err ) { await this . _saveConfig ( DEFAULT_DAEMON_CONFIG , DEFAULT_DAEMON_CONFIG_FILENAME ) ; return DEFAULT_DAEMON_CONFIG ; } const fileContents = await fs . readFile ( fullDaemonConfigPath , { encoding : 'utf8' } ) ; return DaemonConfig . fromString ( fileContents ) ; } param config param filename private static async _saveConfig ( config , filename ) { await fs . mkdir ( DEFAULT_CONFIG_PATH , { recursive : true } ) ; const fullCliConfigPath = path . join ( DEFAULT_CONFIG_PATH , filename ) ; await fs . writeFile ( fullCliConfigPath , JSON . stringify ( config , null , 2 ) ) ; } param content private static _parseContent ( content ) { return content == null ? null : JSON . parse ( content ) ; } param controllers private static _parseControllers ( controllers ) { if ( controllers == null ) { return undefined ; } return controllers . includes ( ',' ) ? controllers . split ( ',' ) : [ controllers ] ; } } const depreciationNotice = ( ) => { console . log ( `${ pc . red ( pc . bold ( 'This command has been deprecated.' ) ) }
Please use the upgraded Glaze CLI instead. 
Please test with the new CLI before reporting any problems. 
${ pc . green ( 'npm i -g @glazed/cli' ) }` ) ; } ;