@@ -549,6 +549,8 @@ public:
549549 " run the server" )
550550 .addSubCommand (" compile" , KJ_BIND_METHOD (*this , getCompile),
551551 " create a self-contained binary" )
552+ .addSubCommand (" measure" , KJ_BIND_METHOD (*this , getMeasure),
553+ " measure the provided worker configuration" )
552554 .addSubCommand (" test" , KJ_BIND_METHOD (*this , getTest),
553555 " run unit tests" )
554556 .build ();
@@ -558,10 +560,13 @@ public:
558560 // "explain": Produces human-friendly description of the config.
559561 } else {
560562 // We already have a config, meaning this must be a compiled binary.
561- auto builder = kj::MainBuilder (context, getVersionString (),
562- " Serve requests based on the compiled config." ,
563- " This binary has an embedded configuration." );
564- return addServeOptions (builder);
563+ return kj::MainBuilder (context, getVersionString (),
564+ " Runs the Workers JavaScript/Wasm runtime." )
565+ .addSubCommand (" serve" , KJ_BIND_METHOD (*this , getServe),
566+ " run the server" )
567+ .addSubCommand (" measure" , KJ_BIND_METHOD (*this , getMeasure),
568+ " measure the provided worker configuration" )
569+ .build ();
565570 }
566571 }
567572
@@ -688,6 +693,16 @@ public:
688693 .build ();
689694 }
690695
696+ kj::MainFunc getMeasure () {
697+ return kj::MainBuilder (context, getVersionString (),
698+ " Measures the provided worker config and outputs the hash." ,
699+ " Loads a worker's code and config in the same way as would be done by the "
700+ " `workerd.createWorker` method, hashes the full config, and returns the hash." )
701+ .expectArg (" <config-file>" , CLI_METHOD (parseWorkerConfigFile))
702+ .callAfterParsing (CLI_METHOD (measure))
703+ .build ();
704+ }
705+
691706 void addImportPath (kj::StringPtr pathStr) {
692707 auto path = fs->getCurrentPath ().evalNative (pathStr);
693708 if (fs->getRoot ().tryOpenSubdir (path) != kj::none) {
@@ -811,6 +826,19 @@ public:
811826 }
812827
813828 void parseConfigFile (kj::StringPtr pathStr) {
829+ config = parseCapnpConfig<config::Config>(pathStr);
830+ // We'll fail at getConfig() if there are multiple top level Config objects.
831+ // The error message says that you have to specify which config to use, but
832+ // it's not clear that there is any mechanism to do that.
833+ util::Autogate::initAutogate (getConfig ().getAutogates ());
834+ }
835+
836+ void parseWorkerConfigFile (kj::StringPtr pathStr) {
837+ workerConfig = parseCapnpConfig<config::Worker>(pathStr);
838+ }
839+
840+ template <typename T>
841+ kj::Maybe<typename T::Reader> parseCapnpConfig (kj::StringPtr pathStr) {
814842 if (pathStr == " -" ) {
815843 // Read from stdin.
816844
@@ -826,8 +854,8 @@ public:
826854#else
827855 auto reader = kj::heap<capnp::StreamFdMessageReader>(STDIN_FILENO, CONFIG_READER_OPTIONS);
828856#endif
829- config = reader->getRoot <config::Config>();
830857 configOwner = kj::mv (reader);
858+ return reader->getRoot <T>();
831859 } else {
832860 // Read file from disk.
833861 auto path = fs->getCurrentPath ().evalNative (pathStr);
@@ -840,11 +868,11 @@ public:
840868 mapping.size () / sizeof (capnp::word));
841869 auto reader = kj::heap<capnp::FlatArrayMessageReader>(words, CONFIG_READER_OPTIONS)
842870 .attach (kj::mv (mapping));
843- config = reader->getRoot <config::Config>();
844871 configOwner = kj::mv (reader);
872+ return reader->getRoot <T>();
845873 } else {
846874 // Interpret as schema file.
847- schemaParser.loadCompiledTypeAndDependencies <config::Config >();
875+ schemaParser.loadCompiledTypeAndDependencies <T >();
848876
849877 parsedSchema = schemaParser.parseFile (
850878 kj::heap<SchemaFileImpl>(fs->getRoot (), fs->getCurrentPath (),
@@ -857,18 +885,14 @@ public:
857885 auto constSchema = nested.asConst ();
858886 auto type = constSchema.getType ();
859887 if (type.isStruct () &&
860- type.asStruct ().getProto ().getId () == capnp::typeId<config::Config >()) {
861- topLevelConfigConstants. add (constSchema );
888+ type.asStruct ().getProto ().getId () == capnp::typeId<T >()) {
889+ return constSchema. as <T>( );
862890 }
863891 }
864892 }
893+ return kj::none;
865894 }
866895 }
867-
868- // We'll fail at getConfig() if there are multiple top level Config objects.
869- // The error message says that you have to specify which config to use, but
870- // it's not clear that there is any mechanism to do that.
871- util::Autogate::initAutogate (getConfig ().getAutogates ());
872896 }
873897
874898 void setConstName (kj::StringPtr name) {
@@ -1075,6 +1099,15 @@ public:
10751099 }
10761100 }
10771101
1102+ void measure () {
1103+ if (hadErrors) context.exit ();
1104+ auto measurement = workerd::server::measureConfig (
1105+ KJ_UNWRAP_OR (workerConfig, CLI_ERROR (" no worker config provided" )));
1106+ auto measurementHex = kj::encodeHex (measurement);
1107+ kj::FdOutputStream out{STDOUT_FILENO};
1108+ out.write (measurementHex.asBytes ().begin (), measurementHex.size ());
1109+ }
1110+
10781111 [[noreturn]] void serve () noexcept {
10791112 serveImpl ([&](jsg::V8System& v8System, config::Config::Reader config) {
10801113#if _WIN32
@@ -1164,6 +1197,7 @@ private:
11641197
11651198 kj::Own<void > configOwner; // backing object for `config`, if it's not `schemaParser`.
11661199 kj::Maybe<config::Config::Reader> config;
1200+ kj::Maybe<config::Worker::Reader> workerConfig;
11671201
11681202 kj::Vector<int > inheritedFds;
11691203
0 commit comments