diff --git a/WORKSPACE b/WORKSPACE index 64f1bd249..3ffb8e826 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,9 +1,9 @@ workspace(name = "nguniversal") http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip", - strip_prefix = "rules_nodejs-0.9.1", - sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f", + # TODO: upgrade once https://github.com/bazelbuild/rules_nodejs/issues/218#issuecomment-395826361 is released + url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip", + strip_prefix = "rules_nodejs-0.8.0", ) http_archive( diff --git a/modules/socket-engine/BUILD.bazel b/modules/socket-engine/BUILD.bazel new file mode 100644 index 000000000..e0187ca3b --- /dev/null +++ b/modules/socket-engine/BUILD.bazel @@ -0,0 +1,46 @@ +load("//tools:defaults.bzl", "ts_library", "ng_module", "ng_package") + +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") + +ng_module( + name = "socket-engine", + srcs = glob([ + "*.ts", + "src/**/*.ts", + ]), + module_name = "@nguniversal/socket-engine", + deps = [ + "//modules/common", + ], +) + +ng_package( + name = "npm_package", + srcs = [ + ":package.json", + ], + entry_point = "modules/socket-engine/index.js", + readme_md = ":README.md", + deps = [ + ":socket-engine", + "//modules/common", + ], +) + +ts_library( + name = "unit_test_lib", + testonly = True, + srcs = glob([ + "spec/**/*.spec.ts", + ]), + deps = [ + ":socket-engine", + ], +) + +jasmine_node_test( + name = "unit_test", + srcs = [":unit_test_lib"], +) diff --git a/modules/socket-engine/README.md b/modules/socket-engine/README.md new file mode 100644 index 000000000..6561ac42e --- /dev/null +++ b/modules/socket-engine/README.md @@ -0,0 +1,44 @@ +# Angular Universal Socket Engine + +Framework and Platform agnostic Angular Universal rendering. + +## Usage Server + +`npm install @nguniversal/socket-engine @nguniversal/common --save` + +```js +const socketEngine = require('@nguniversal/socket-engine'); + +// * NOTE :: leave this as require() since this file is built Dynamically from webpack +const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main'); + +socketEngine.startSocketEngine(AppServerModuleNgFactory); +``` +This will the socket engine which internally hosts a TCP Socket server. +The default port is `9090` and host of `localhost` +You may want to leave this as a plain `.js` file since it is so simple and to make deploying it easier, but it can be easily transpiled from Typescript. + +## Usage Client + +Your client can be whatever language, framework or platform you like. +As long as it can connect to a TCP Socket (which all frameworks can) then you're good to go. + +This example will use JS for simplicity +```typescript +import * as net from 'net'; + +const client = net.createConnection(9090, 'localhost', () => { + console.log('connected to SSR server'); +}); + +client.on('data', data => { + const res = JSON.parse(data.toString()) as SocketEngineResponse; + expect(res.id).toEqual(1); + expect(res.html).toEqual(template); + server.close(); + done(); +}); + +const renderOptions = {id: 1, url: '/path', document: ''} as SocketEngineRenderOptions; +client.write(JSON.stringify(renderOptions)); +``` \ No newline at end of file diff --git a/modules/socket-engine/index.ts b/modules/socket-engine/index.ts new file mode 100644 index 000000000..45965af3d --- /dev/null +++ b/modules/socket-engine/index.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export * from './public_api'; diff --git a/modules/socket-engine/package.json b/modules/socket-engine/package.json new file mode 100644 index 000000000..ef3fbd22b --- /dev/null +++ b/modules/socket-engine/package.json @@ -0,0 +1,26 @@ +{ + "name": "@nguniversal/socket-engine", + "version": "0.0.0-PLACEHOLDER", + "description": "Socket Engine for running Server Angular Apps", + "license": "MIT", + "keywords": [ + "socket", + "ssr", + "universal" + ], + "peerDependencies": { + "@nguniversal/common": "0.0.0-PLACEHOLDER", + "@angular/core": "NG_VERSION" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/universal" + }, + "bugs": { + "url": "https://github.com/angular/universal/issues" + }, + "homepage": "https://github.com/angular/universal" +} diff --git a/modules/socket-engine/public_api.ts b/modules/socket-engine/public_api.ts new file mode 100644 index 000000000..7cea2dac2 --- /dev/null +++ b/modules/socket-engine/public_api.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export { startSocketEngine, SocketEngineResponse, SocketEngineRenderOptions } from './src/main'; diff --git a/modules/socket-engine/spec/index.spec.ts b/modules/socket-engine/spec/index.spec.ts new file mode 100644 index 000000000..f9e7447f1 --- /dev/null +++ b/modules/socket-engine/spec/index.spec.ts @@ -0,0 +1,91 @@ + +import { ServerModule } from '@angular/platform-server'; +import { NgModule, Component } from '@angular/core'; +import 'zone.js'; + +import { BrowserModule } from '@angular/platform-browser'; +import { startSocketEngine, SocketEngineResponse, + SocketEngineRenderOptions } from '@nguniversal/socket-engine'; +import * as net from 'net'; + +export function makeTestingModule(template: string, component?: any): any { + @Component({ + selector: 'root', + template: template + }) + class MockComponent {} + @NgModule({ + imports: [ServerModule, BrowserModule.withServerTransition({appId: 'mock'})], + declarations: [component || MockComponent], + bootstrap: [component || MockComponent] + }) + class MockServerModule {} + return MockServerModule; +} + +async function sendAndRecieve(renderOptions: SocketEngineRenderOptions, template = '') { + return new Promise(async(resolve, _reject) => { + + const appModule = makeTestingModule(template); + const server = await startSocketEngine(appModule); + + const client = net.createConnection(9090, 'localhost', () => { + client.write(JSON.stringify(renderOptions)); + }); + + client.on('data', data => { + const res = JSON.parse(data.toString()) as SocketEngineResponse; + server.close(); + resolve(res); + }); + }); +} + +describe('test runner', () => { + it('should render a basic template', async (done) => { + const template = `${new Date()}`; + const renderOptions = {id: 1, url: '/path', + document: ''} as SocketEngineRenderOptions; + const result = await sendAndRecieve(renderOptions, template); + expect(result.html).toContain(template); + done(); + }); + it('should return the same id', async(done) => { + const id = Math.random(); + const renderOptions = {id , url: '/path', + document: ''} as SocketEngineRenderOptions; + const result = await sendAndRecieve(renderOptions); + expect(result.id).toEqual(id); + done(); + }); + it('should return an error if it cant render', async(done) => { + @Component({ + selector: 'root', + template: '' + }) + class MockComponent {constructor(_illMakeItThrow: '') {}} + const appModule = makeTestingModule('', MockComponent); + const server = await startSocketEngine(appModule); + + const client = net.createConnection(9090, 'localhost', () => { + const renderOptions = {id: 1, url: '/path', + document: ''} as SocketEngineRenderOptions; + client.write(JSON.stringify(renderOptions)); + }); + + client.on('data', data => { + const res = JSON.parse(data.toString()) as SocketEngineResponse; + server.close(); + expect(res.error).not.toBeNull(); + done(); + }); + }); + it('should return an error if it cant render', async(done) => { + const template = `${new Date()}`; + const renderOptions = {id: 1, url: '/path', + document: ''} as SocketEngineRenderOptions; + const result = await sendAndRecieve(renderOptions, template); + expect(result.error).toBeUndefined(); + done(); + }); +}); diff --git a/modules/socket-engine/src/main.ts b/modules/socket-engine/src/main.ts new file mode 100644 index 000000000..0a9faad39 --- /dev/null +++ b/modules/socket-engine/src/main.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { ɵCommonEngine as CommonEngine, + ɵRenderOptions as RenderOptions } from '@nguniversal/common/engine'; +import { NgModuleFactory, Type } from '@angular/core'; +import * as net from 'net'; + +export interface SocketEngineServer { + close: () => void; +} + +export interface SocketEngineRenderOptions extends RenderOptions { + id: number; +} + +export interface SocketEngineResponse { + id: number; + html: string|null; + error?: Error; +} + +export function startSocketEngine( + moduleOrFactory: Type<{}> | NgModuleFactory<{}>, + host = 'localhost', + port = 9090 +): Promise { + return new Promise((resolve, _reject) => { + const engine = new CommonEngine(moduleOrFactory); + + const server = net.createServer(socket => { + socket.on('data', async buff => { + const message = buff.toString(); + const renderOptions = JSON.parse(message) as SocketEngineRenderOptions; + try { + const html = await engine.render(renderOptions); + socket.write(JSON.stringify({html, id: renderOptions.id} as SocketEngineResponse)); + } catch (error) { + // send the error down to the client then rethrow it + socket.write(JSON.stringify({html: null, + id: renderOptions.id, error} as SocketEngineResponse)); + throw error; + } + }); + }); + + server.listen(port, host); + resolve({close: () => server.close()}); + }); +} + diff --git a/yarn.lock b/yarn.lock index 3849fe7d4..aea77ce90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -125,8 +125,8 @@ "@types/range-parser" "*" "@types/express@^4.0.39": - version "4.16.0" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.0.tgz#6d8bc42ccaa6f35cf29a2b7c3333cb47b5a32a19" + version "4.11.1" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.11.1.tgz#f99663b3ab32d04cb11db612ef5dd7933f75465b" dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "*" @@ -166,8 +166,8 @@ "@types/node" "*" "@types/jasmine@^2.8.6": - version "2.8.8" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.8.tgz#bf53a7d193ea8b03867a38bfdb4fbb0e0bf066c9" + version "2.8.7" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.7.tgz#3fe583928ae0a22cdd34cedf930eeffeda2602fd" "@types/joi@*": version "13.0.8" @@ -192,20 +192,20 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" "@types/node@*": - version "10.3.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.2.tgz#3840ec6c12556fdda6e0e6d036df853101d732a4" + version "10.1.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6" "@types/node@6.0.84": version "6.0.84" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.84.tgz#193ffe5a9f42864d425ffd9739d95b753c6a1eab" "@types/node@^6.0.46": - version "6.0.112" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.112.tgz#0f37473b1d1ecd30c8bf57215ef4fb558f99cc86" + version "6.0.111" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.111.tgz#85f880a1bab78d395a5de9bcb5319e73a0c31400" "@types/node@^9.4.6": - version "9.6.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.21.tgz#4563c26a53531c5aca943065fcdf2dd562f63cd4" + version "9.6.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" "@types/podium@*": version "1.0.0" @@ -244,8 +244,8 @@ "@types/node" "*" JSONStream@^1.0.4: - version "1.3.3" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf" + version "1.3.2" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea" dependencies: jsonparse "^1.2.0" through ">=2.2.7 <3" @@ -277,8 +277,8 @@ acorn@^4.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" acorn@^5.2.1: - version "5.6.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.6.2.tgz#b1da1d7be2ac1b4a327fb9eab851702c5045b4e7" + version "5.5.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" addressparser@1.0.1: version "1.0.1" @@ -490,8 +490,8 @@ assert@^1.4.1: util "0.10.3" ast-types@0.x.x: - version "0.11.5" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.5.tgz#9890825d660c03c28339f315e9fa0a360e31ec28" + version "0.11.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.4.tgz#76f930930e9571851ba282a9a0f6923f29f6be2f" async-each@^1.0.0: version "1.0.1" @@ -506,10 +506,10 @@ async@1.x, async@^1.4.0: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.1.4, async@~2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" dependencies: - lodash "^4.17.10" + lodash "^4.14.0" asynckit@^0.4.0: version "0.4.0" @@ -973,8 +973,8 @@ color-name@^1.1.1: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" colors@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e" + version "1.2.5" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" colour@~0.7.1: version "0.7.1" @@ -1231,8 +1231,8 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" core-js@^2.2.0: - version "2.5.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" + version "2.5.6" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.6.tgz#0fe6d45bf3cac3ac364a9d72de7576f4eb221b9d" core-js@~2.3.0: version "2.3.0" @@ -1386,9 +1386,9 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" +deep-extend@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" deep-is@~0.1.3: version "0.1.3" @@ -2904,7 +2904,7 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" -lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.5.0: +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.5.0: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -2917,8 +2917,8 @@ log4js@^1.1.1: streamroller "^0.4.0" log4js@^2.3.9: - version "2.8.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-2.8.0.tgz#9f42fcc4fe82004dfd136604dd7bc1c35d61d6c5" + version "2.6.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-2.6.1.tgz#497289259b5b941dd518f6ce219e6768aec87a96" dependencies: circular-json "^0.5.4" date-format "^1.2.0" @@ -3157,11 +3157,11 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" +minipass@^2.2.1, minipass@^2.2.4: + version "2.3.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.1.tgz#4e872b959131a672837ab3cb554962bc84b1537d" dependencies: - safe-buffer "^5.1.2" + safe-buffer "^5.1.1" yallist "^3.0.0" minizlib@^1.1.0: @@ -3844,10 +3844,10 @@ raw-body@2.3.3, raw-body@^2.2.0: unpipe "1.0.0" rc@^1.1.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + version "1.2.7" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.7.tgz#8a10ca30d588d00464360372b890d06dacd02297" dependencies: - deep-extend "^0.6.0" + deep-extend "^0.5.1" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" @@ -4052,8 +4052,8 @@ request@2.75.x: tunnel-agent "~0.4.1" request@^2.0.0, request@^2.74.0, request@^2.78.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + version "2.86.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.86.0.tgz#2b9497f449b0a32654c081a5cf426bbfb5bf5b69" dependencies: aws-sign2 "~0.7.0" aws4 "^1.6.0" @@ -4063,6 +4063,7 @@ request@^2.0.0, request@^2.74.0, request@^2.78.0: forever-agent "~0.6.1" form-data "~2.3.1" har-validator "~5.0.3" + hawk "~6.0.2" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -4168,8 +4169,8 @@ rollup-plugin-uglify@^2.0.1: uglify-js "^3.0.9" rollup-pluginutils@^2.0.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.0.tgz#478ace04bd7f6da2e724356ca798214884738fc4" + version "2.2.0" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.2.0.tgz#64ba3f29988b84322bafa188a9f99ca731c95354" dependencies: estree-walker "^0.5.2" micromatch "^2.3.11" @@ -4571,8 +4572,8 @@ stream-browserify@^2.0.1: readable-stream "^2.0.2" stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + version "2.8.2" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.2.tgz#4126e8c6b107004465918aa2fc35549e77402c87" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" @@ -4700,12 +4701,12 @@ systemjs@0.19.43: when "^3.7.5" tar@^4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" + version "4.4.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.2.tgz#60685211ba46b38847b1ae7ee1a24d744a2cd462" dependencies: chownr "^1.0.1" fs-minipass "^1.2.5" - minipass "^2.3.3" + minipass "^2.2.4" minizlib "^1.1.0" mkdirp "^0.5.0" safe-buffer "^5.1.2" @@ -4845,8 +4846,8 @@ tsickle@^0.29.0: source-map-support "^0.5.0" tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.9.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.2.tgz#8be0cc9a1f6dc7727c38deb16c2ebd1a2892988e" + version "1.9.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.1.tgz#a5d1f0532a49221c87755cfcc89ca37197242ba7" tslint@^5.9.1: version "5.10.0" @@ -4924,8 +4925,8 @@ uglify-js@^2.6, uglify-js@^2.8.14: uglify-to-browserify "~1.0.0" uglify-js@^3.0.9: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.0.tgz#796762282b5b5f0eafe7d5c8c708d1d7bd5ba11d" + version "3.3.25" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.25.tgz#3266ccb87c5bea229f69041a0296010d6477d539" dependencies: commander "~2.15.0" source-map "~0.6.1" @@ -5001,8 +5002,8 @@ uws@~9.14.0: resolved "https://registry.yarnpkg.com/uws/-/uws-9.14.0.tgz#fac8386befc33a7a3705cbd58dc47b430ca4dd95" v8flags@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.1.tgz#42259a1461c08397e37fe1d4f1cfb59cad85a053" + version "3.1.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.0.tgz#246a34a8158c0e1390dcb758e1140e5d004e230b" dependencies: homedir-polyfill "^1.0.1"