/
githubmirror
/
meteor
Обзор
Документация
Войти
/
githubmirror
/
meteor
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
devel
tools/packaging/package-client.js
896 строк
31 KB
Nacho Codoñer
add a new devOnly mode to discard completely for production bundle specific packages
19 июн 2025, 17:50
19 июн 2025, 17:50
036f39c
Код
Авторство
О чём код?
var _ = require('underscore'); var config = require('../meteor-services/config.js'); var httpHelpers = require('../utils/http-helpers.js'); var release = require('./release.js'); var files = require('../fs/files'); var utils = require('../utils/utils.js'); var buildmessage = require('../utils/buildmessage.js'); var compiler = require('../isobuild/compiler.js'); var authClient = require('../meteor-services/auth-client.js'); var catalog = require('./catalog/catalog.js'); var projectContextModule = require('../project-context.js'); var colonConverter = require('../utils/colon-converter.js'); var Profile = require('../tool-env/profile').Profile; import { requestGarbageCollection } from "../utils/gc.js"; // Opens a DDP connection to a package server. Loads the packages needed for a // DDP connection, then calls DDP connect to the package server URL in config, // using a current user-agent header composed by http-helpers.js. var openPackageServerConnection = function (packageServerUrl) { var serverUrl = packageServerUrl || config.getPackageServerUrl(); return authClient.openServiceConnection(serverUrl); }; // We don't let the user upload a blank README for UX reasons, but we would // prefer that the server move to a world with 'readme' files for everything in // the future. As a way to breach these interfaces, for now, we are going to // upload blank documentation files when null docs are requested. // // This function generates a Readme object for a blank readme file, as well as // the file itself. var generateBlankReadme = function () { return { contents: "", excerpt: "", hash: files.blankHash }; }; // Save a readme file to a temporary path. var saveReadmeToTmp = async function (readmeInfo) { var tempReadmeDir = files.mkdtemp('readme'); var readmePath = files.pathJoin(tempReadmeDir, "Readme.md"); await files.writeFileAtomically(readmePath, readmeInfo.contents); return readmePath; }; // Given a connection, makes a call to the package server. (Checks to see if // the connection is connected, and reconnects if needed -- a workaround for // the fact that connections in the tool do not reconnect) exports.callPackageServer = async function (conn, ...args) { // XXX This is broken since it doesn't actually replace the conn in the // caller, so it'll happen on every subsequent call if (!conn.connected) { conn.close(); conn = await exports.loggedInPackagesConnection(); } return conn.call(...args); }; var callPackageServerBM = exports.callPackageServerBM = async function (...args) { buildmessage.assertInJob(); try { return exports.callPackageServer.apply(null, args); } catch (e) { buildmessage.error(e.reason || e.message); return null; } }; // Requests and returns one page of new package data that we haven't cached on // disk. We assume that data is cached chronologically, so essentially, we are // asking for a diff from the last time that we did this. // Takes in: // - conn: the connection to use (does not have to be logged in) // - syncToken: a syncToken object to be sent to the server that // represents the last time that we talked to the server. // - options: // - useShortPages (Boolean). Ask the server for pages of ~3 records // instead of ~100, for testing pagination. // // Returns an object, containing the following fields: // - syncToken: a new syncToken object, that we can pass to the server in the future. // - collections: an object keyed by the name of server collections, with the // records as an array of javascript objects. var loadRemotePackageData = async function (conn, syncToken, options) { options = options || {}; // Did we get disconnected between retries somehow? Then we should open a new // connection. We shouldn't use the callPackageServer method here though, // since we don't need to authenticate. if (!conn.connected) { conn.close(); conn = await openPackageServerConnection(); } var syncOpts = {}; if (options && options.useShortPages) { syncOpts.shortPagesForTest = options.useShortPages; } if (options && options.compressCollections) { syncOpts.compressCollections = options.compressCollections; } return await conn.call('syncNewPackageData', syncToken, syncOpts); }; // Contacts the package server to get the latest diff and writes changes to // disk. // // Takes in the dataStore, which is an example of the remote catalog. Contacts // the package server and updates the sql database with the most recent // information. // // Returns null if contacting the server times out, or an object with the // following keys: // resetData : true if we should reset the database, otherwise false. // connectionFailed: true if we failed to connect to the server. // // options can include: // - packageStorageFile: String. The file to write the data to (overrides // `config.getPackageStorage()`) // - packageServerUrl: String. The package server (overrides // `config.getPackageServerUrl()`) // - useShortPages: Boolean. Request short pages of ~3 records from the // server, instead of ~100 that it would send otherwise exports.updateServerPackageData = async function (dataStore, options) { return await buildmessage.enterJob('updating package catalog', async function () { return await _updateServerPackageData(dataStore, options); }); }; var _updateServerPackageData = async function (dataStore, options) { var self = this; options = options || {}; if (dataStore === null) { throw Error("Data store expected"); } var done = false; var ret = {resetData: false}; // For now, we don't have a great progress metric, so just use a spinner var useProgressbar = false; var start = undefined; // Guess that we're about an hour behind, as an opening guess var state = { current: 0, end: 60 * 60 * 1000, done: false}; useProgressbar && buildmessage.reportProgress(state); var conn = await openPackageServerConnection(options.packageServerUrl); // Provide some progress indication for connection // XXX though it is just a hack state.current = 1; useProgressbar && buildmessage.reportProgress(state); var getSomeData = async function () { var syncToken = (await dataStore.getSyncToken()) || {format: "1.1"}; if (!start) { start = {}; start.builds = syncToken.builds; start.versions = syncToken.versions; state.end = (Date.now() - start.builds) + (Date.now() - start.versions); } // XXX: This is a hack... syncToken should have a % done state.current = (syncToken.builds - start.builds) + (syncToken.versions - start.versions); useProgressbar && buildmessage.reportProgress(state); var compress = !!process.env.METEOR_CATALOG_COMPRESS_RPCS; // (loadRemotePackageData may throw) var remoteData = await loadRemotePackageData(conn, syncToken, { useShortPages: options.useShortPages, compressCollections: compress }); // Is the remote server telling us to ignore everything we've heard before? // OK, we can do that. if (remoteData.resetData) { dataStore.reset(); // The caller may want to take this as a cue to delete packages from the // tropohouse. ret.resetData = true; } if (remoteData.collectionsCompressed) { var zlib = require('zlib'); var colsGzippedBuffer = Buffer.from( remoteData.collectionsCompressed, 'base64'); var colsJSON = await new Promise((resolve, reject) => { zlib.gunzip(colsGzippedBuffer, (err, res) => { err ? reject(err) : resolve(res); }); }); remoteData.collections = JSON.parse(colsJSON); delete remoteData.collectionsCompressed; } // We always write to the data store; the fact there is no data is itself // data! e.g. the last-refresh timestamp var syncComplete = _.isEqual(remoteData.collections, {}) || remoteData.upToDate; await dataStore.insertData(remoteData, syncComplete); // If there is no new data from the server, don't bother writing things to // disk (unless we were just told to reset everything). if (!remoteData.resetData && _.isEqual(remoteData.collections, {})) { done = true; return; } if (remoteData.upToDate) { done = true; } }; try { while (!done) { await getSomeData(); requestGarbageCollection(); } } finally { conn.close(); } return ret; }; _updateServerPackageData = Profile('package-client _updateServerPackageData', _updateServerPackageData); // Returns a logged-in DDP connection to the package server, or null if // we cannot log in. If an error unrelated to login occurs // (e.g. connection to package server times out), then it will be // thrown. /** * * @return {Promise<*>} */ exports.loggedInPackagesConnection = async function () { return await authClient.loggedInConnection( config.getPackageServerUrl(), config.getPackageServerDomain(), "package-server" ); }; // XXX this is missing a few things. In retrospect a better approach here might // be to actually make "save source somewhere else" or perhaps "add source // to tarball" be part of the package build itself... var bundleSource = async function (isopack, includeSources, packageDir) { buildmessage.assertInJob(); var name = isopack.name; var tempDir = files.mkdtemp('build-source-package-'); var packageTarName = name + '-' + isopack.version + '-source'; var dirToTar = files.pathJoin(tempDir, 'source', colonConverter.convert(packageTarName)); // XXX name probably needs to be escaped for windows? // XXX note that publish-for-arch thinks it knows how this tarball is laid // out, which is a bit of a shame var sourcePackageDir = files.pathJoin(dirToTar, colonConverter.convert(name)); if (! files.mkdir_p(sourcePackageDir)) { buildmessage.error('Failed to create temporary source directory: ' + sourcePackageDir); return null; } // We copy source files into a temp directory and then tar up the temp // directory. It would be great if we could avoid the copy, but as far // as we can tell, this is the only way to get a tarball with the // directory structure that we want (<package name>-<version-source/ // at the top level). _.each(includeSources, function (f) { const from = files.pathJoin(packageDir, f); const to = files.pathJoin(sourcePackageDir, f); if (files.exists(from)) { files.copyFile(from, to); } }); // Write a package map to `.versions` inside the source tarball. Note that // this differs in two ways from the `.versions` file that is maintained // inside standalone packages by 'meteor publish': // (a) It only contains the direct, directly implied, and linked-into-plugin // dependencies of the package, not all transitive dependencies. // (b) It is ALWAYS put into the source tarball, even if the package came // from inside an app, whereas the package-source-tree .versions file // is only used for standalone packages var packageMapFilename = files.pathJoin(sourcePackageDir, '.versions'); if (files.exists(packageMapFilename)) { throw Error(".versions file already exists? " + packageMapFilename); } var pluginProviderPackageMap = isopack.pluginProviderPackageMap; if (! pluginProviderPackageMap) { throw Error("no pluginProviderPackageMap on isopack?"); } var packageMapFile = new projectContextModule.PackageMapFile({ filename: packageMapFilename }); await packageMapFile.write(pluginProviderPackageMap); // We put this inside the temp dir because mkdtemp makes sure that the // temp dir gets cleaned up on process exit, so we don't have to worry // about cleaning up our tarball (or our copied source files) // ourselves. var sourceTarball = files.pathJoin(tempDir, packageTarName + '.tgz'); await files.createTarball(dirToTar, sourceTarball); var tarballHash = files.fileHash(sourceTarball); var treeHash = files.treeHash(dirToTar); return { sourceTarball: sourceTarball, tarballHash: tarballHash, treeHash: treeHash }; }; // Uploads a file at a filepath to the HTTP put URL. // // Returns true on success and false on failure. var uploadFile = async function (putUrl, filepath) { buildmessage.assertInJob(); var size = files.stat(filepath).size; var rs = files.createReadStream(filepath); try { // Use getUrl instead of request, to throw on 4xx/5xx. await httpHelpers.getUrl({ method: 'PUT', url: putUrl, headers: { 'content-length': size, 'content-type': 'application/octet-stream', 'x-amz-acl': 'public-read' }, bodyStream: rs, bodyStreamLength: size }); } catch (err) { // XXX: getUrl's error handling is terrible and we should fix it there. buildmessage.error(typeof err === "string" ? err : err.toString()); return false; } finally { rs.close(); } return true; }; exports.uploadFile = uploadFile; export async function bundleBuild(isopack, isopackCache) { buildmessage.assertInJob(); var tempDir = files.mkdtemp('bp-'); var packageTarName = isopack.tarballName(); var tarInputDir = files.pathJoin(tempDir, packageTarName); // Note that we do need to do this even though we already have the isopack on // disk in an IsopackCache, because we don't want to include // isopack-buildinfo.json. (We don't include it because we're not passing // includeIsopackBuildInfo to saveToPath here.) await isopack.saveToPath(tarInputDir, { // When publishing packages that don't use new registerCompiler plugins, // make sure that old Meteors can use it too includePreCompilerPluginIsopackVersions: true, isopackCache, }); var buildTarball = files.pathJoin(tempDir, packageTarName + '.tgz'); await files.createTarball(tarInputDir, buildTarball); var tarballHash = files.fileHash(buildTarball); var treeHash = files.treeHash(tarInputDir, { // We don't include any package.json from an npm module in the tree hash, // because npm isn't super consistent about what it puts in there (eg, does // it include the "readme" field)? This ends up leading to spurious // differences. The tree hash will still notice any actual CODE changes in // the npm packages. ignore: function (relativePath) { var pieces = relativePath.split(files.pathSep); return pieces.length && _.last(pieces) === 'package.json' && pieces.includes('npm'); } }); return { buildTarball: buildTarball, tarballHash: tarballHash, treeHash: treeHash }; } async function createBuiltPackage(isopack, isopackCache) { buildmessage.assertInJob(); var name = isopack.name; // Note: we really want to do this before createPackageBuild, because the URL // we get from createPackageBuild will expire! var bundleResult; await buildmessage.enterJob("bundling build for " + name, async function () { bundleResult = await bundleBuild(isopack, isopackCache); }); if (buildmessage.jobHasMessages()) { return; } return bundleResult; } var publishBuiltPackage = async function (conn, isopack, bundleResult) { buildmessage.assertInJob(); var name = isopack.name; var uploadInfo; await buildmessage.enterJob('creating package build for ' + name, async function () { uploadInfo = await callPackageServerBM(conn, 'createPackageBuild', { packageName: isopack.name, version: isopack.version, buildArchitectures: isopack.buildArchitectures() }); }); if (buildmessage.jobHasMessages()) { return; } await buildmessage.enterJob("uploading build", async function () { await uploadFile(uploadInfo.uploadUrl, bundleResult.buildTarball); }); if (buildmessage.jobHasMessages()) { return; } await buildmessage.enterJob('publishing package build for ' + name, async function () { try { await callPackageServerBM(conn, 'publishPackageBuild', uploadInfo.uploadToken, bundleResult.tarballHash, bundleResult.treeHash); } catch (e) { buildmessage.error(e.message); } }); if (buildmessage.jobHasMessages()) { return; } }; export async function createAndPublishBuiltPackage(conn, isopack, isopackCache) { await publishBuiltPackage( conn, isopack, await createBuiltPackage(isopack, isopackCache), ); } // Handle an error thrown on trying to connect to the package server. exports.handlePackageServerConnectionError = function (error) { authClient.handleConnectionError(error, "package server"); }; // Update the package metadata in the server catalog. Change the docs, // descriptions and the Git URL to new values. // // options: // - packageSource: the packageSource for this package. // - readmeInfo: null, or an object containing docs information for this package. // - connection: the open, logged-in connection over which we should talk to the // package server. DO NOT CLOSE this connection here. // // Return true on success and an error code otherwise. exports.updatePackageMetadata = async function (options) { buildmessage.assertInJob(); var packageSource = options.packageSource; var conn = options.connection; var readmeInfo = options.readmeInfo; var name = packageSource.name; var version = packageSource.version; if (! version) { buildmessage.error( "Package cannot be updated because it doesn't have a version"); return; } // For now, documentation is optional on the client, so we have to give people // a way to remove it with 'documentation: null'. if (! readmeInfo) { readmeInfo = generateBlankReadme(); } var dataToUpdate = { git: packageSource.metadata.git || "", description: packageSource.metadata.summary, longDescription: readmeInfo.excerpt }; // Check that the metadata fits under the established limits, and give helpful // feedback. if (! dataToUpdate["description"]) { buildmessage.error("Please provide a short description to use in 'meteor search'"); return; } if (dataToUpdate["description"] && dataToUpdate["description"].length > 100) { buildmessage.error("Summary must be under 100 chars."); return; } if (dataToUpdate["longDescription"].length > 1500) { buildmessage.error( "Longform package description is too long. Meteor uses the section of " + "the Markdown documentation file between the first and second " + "headings. That section must be less than 1500 characters long."); return; } // Update the general metadata. var versionIdentifier = { packageName: name, version: version }; await buildmessage.enterJob('updating metadata', async function () { await callPackageServerBM( conn, "changeVersionMetadata", versionIdentifier, dataToUpdate); }); if (buildmessage.jobHasMessages()) { return; } // Upload the new Readme. await buildmessage.enterJob('uploading documentation', async function () { var readmePath = await saveReadmeToTmp(readmeInfo); var uploadInfo = await callPackageServerBM(conn, "createReadme", versionIdentifier); if (!uploadInfo) { return; } if (!await uploadFile(uploadInfo.url, readmePath)) { return; } await callPackageServerBM( conn, "publishReadme", uploadInfo.uploadToken, { hash: readmeInfo.hash }); }); if (buildmessage.jobHasMessages()) { return; } }; // Publish the package information into the server catalog. Create new records // for the package (if needed), the version and the build; upload source and // isopack. // // options: // - packageSource: the packageSource for this package. // - connection: the open, logged-in connection over which we should talk to the // package server. DO NOT CLOSE this connection here. // - projectContext: the (probably temporary) ProjectContext to use. Must have\ // already built local packages // - new: this package is new, we should call createPackage to create a new // package record. // - existingVersion: we expect the version to exist already, and for us // to merely be providing a new build of the same source // - doNotPublishBuild: do not publish the build of this package. // // Return true on success and an error code otherwise. exports.publishPackage = async function (options) { buildmessage.assertInJob(); var packageSource = options.packageSource; var conn = options.connection; var projectContext = options.projectContext; var name = packageSource.name; var version = packageSource.version; if (options.new && options.existingVersion) { throw Error("is it new or does it exist?!?"); } // Check that the package name is valid. utils.validatePackageName(name, { useBuildmessage: true }); if (buildmessage.jobHasMessages()) { return; } // Check that we have a version. if (! version) { buildmessage.error( "Package cannot be published because it doesn't have a version"); return; } // Check that the version description is under the character limit. (We check // all string limits on the server, but this is the one that is mostly likely // to be wrong) if (! packageSource.metadata.summary) { buildmessage.error( "Please describe what your package does. Set a summary " + "in Package.describe in package.js."); return; } if (packageSource.metadata.summary.length > 100) { buildmessage.error("Summary must be under 100 chars."); return; } // Check that we are an authorized maintainer of this package. if (!options['new']) { var packRecord = await catalog.official.getPackage(name); if (! packRecord) { buildmessage.error( 'There is no package named ' + name + '. If you are creating a new package, use the --create flag.'); return; } if (!await exports.amIAuthorized(name, conn, false)) { buildmessage.error( 'You are not an authorized maintainer of ' + name + '. Only ' + 'authorized maintainers may publish new versions.'); } } // Check that our documentation exists (or we know that it doesn't) and has // been filled out. var readmeInfo = await buildmessage.enterJob( "processing documentation", function () { return packageSource.processReadme(); }); if (buildmessage.jobHasMessages()) { return; } if (readmeInfo && (readmeInfo.hash === files.blankHash)) { buildmessage.error( "Your documentation file is blank, so users may have trouble figuring " + "out how to use your package. Please fill it out, or " + "set 'documentation: null' in your Package.describe"); return; } if (readmeInfo && readmeInfo.excerpt.length > 1500) { buildmessage.error( "Longform package description is too long. Meteor uses the section of " + "the Markdown documentation file between the first and second " + "headings. That section must be less than 1500 characters long."); return; } // We don't let the user upload a blank README for UX reasons, but we would // prefer that the server move to a world with 'readme' files for everything // in the future. This helps unite these interfaces, and makes our code easier // to reason about in the future. if (! readmeInfo) { readmeInfo = generateBlankReadme(); } var readmePath = await saveReadmeToTmp(readmeInfo); var packageDeps = packageSource.getDependencyMetadata(); // Check that the package does not have any unconstrained references. _.each(packageDeps, function(refs, label) { if (refs.constraint == null) { if (packageSource.isCore && files.inCheckout() && projectContext.localCatalog.getPackage(label)) { // Core package is using or implying another core package, // without a version number. We fill in the version number. // (Well, we're assuming that the other package is core and // not some other sort of local package.) var versionString = projectContext.localCatalog.getLatestVersion(label).version; // modify the constraint on this dep that will be sent to troposphere refs.constraint = versionString; } else if (label === "meteor") { // HACK: We are willing to publish a package with a "null" // constraint on the "meteor" package to troposphere. This // happens for non-core packages when not running from a // checkout, because all packages implicitly depend on the // "meteor" package, but do not necessarily specify an // explicit version for it, and we don't have a great way to // choose one here. // XXX come back to this, especially if we are incrementing the // major version of "meteor". hopefully we will have more data // about the package system by then. } else { buildmessage.error( "You must specify a version constraint for package " + label); } } }); if (buildmessage.jobHasMessages()) { return; } var isopack = projectContext.isopackCache.getIsopack(name); if (! isopack) { throw Error("no isopack " + name); } // If we aren't able to include legacy builds in this version, make sure that // it has a fake dependency on isobuild:isopack-2 so that old versions of // Isobuild won't accidentally use it. if (!isopack.canWriteLegacyBuilds() && !_.has(packageDeps, 'isobuild:isopack-2')) { packageDeps['isobuild:isopack-2'] = { constraint: '1.0.0', // Arbitrary arch here; nothing other than meteor show really pays // attention to reference arch anymore, because we no longer pay attention // to the arch with the constraint in Version Solver. references: [{arch: 'os'}], }; } var sourceFiles = isopack.getSourceFilesUnderSourceRoot( packageSource.sourceRoot); if (! sourceFiles) { throw Error("isopack doesn't know what its source files are?"); } // We need to have built the test package to get all of its sources, even // though we're not publishing a BUILD for the test package. if (packageSource.testName) { var testIsopack = projectContext.isopackCache.getIsopack( packageSource.testName); if (! testIsopack) { throw Error("no testIsopack " + packageSource.testName); } var testSourceFiles = testIsopack.getSourceFilesUnderSourceRoot( packageSource.sourceRoot); if (! testSourceFiles) { throw Error("test isopack doesn't know what its source files are?"); } sourceFiles = _.union(sourceFiles, testSourceFiles); } var sourceBundleResult; await buildmessage.enterJob("bundling source for " + name, async function () { sourceBundleResult = await bundleSource( isopack, sourceFiles, packageSource.sourceRoot); }); if (buildmessage.jobHasMessages()) { return; } // Create the package. Check that the metadata exists. if (options.new) { await buildmessage.enterJob("creating package " + name, async function () { await callPackageServerBM(conn, 'createPackage', { name: packageSource.name }); }); if (buildmessage.jobHasMessages()) { return; } } if (options.existingVersion) { var existingRecord = await catalog.official.getVersion(name, version); if (! existingRecord) { buildmessage.error("Version does not exist."); return; } if (existingRecord.source.treeHash !== sourceBundleResult.treeHash) { buildmessage.error("Package source differs from the existing version."); return; } if (! options.doNotPublishBuild) { await createAndPublishBuiltPackage( conn, isopack, projectContext.isopackCache); if (buildmessage.jobHasMessages()) { return; } } // XXX check that we're actually providing something new? } else { var uploadInfo; await buildmessage.enterJob("pre-publishing package " + name, async function () { var uploadRec = { packageName: packageSource.name, version: version, description: packageSource.metadata.summary, longDescription: readmeInfo.excerpt, git: packageSource.metadata.git, compilerVersion: compiler.BUILT_BY, containsPlugins: packageSource.containsPlugins(), debugOnly: packageSource.debugOnly, prodOnly: packageSource.prodOnly, testOnly: packageSource.testOnly, devOnly: packageSource.devOnly, deprecated: packageSource.deprecated, deprecatedMessage: packageSource.deprecatedMessage, exports: packageSource.getExports(), releaseName: release.current.name, dependencies: packageDeps }; uploadInfo = await callPackageServerBM(conn, 'createPackageVersion', uploadRec); }); if (buildmessage.jobHasMessages()) { return; } // XXX If package version already exists, print a nice error message // telling them to try 'meteor publish-for-arch' if they want to // publish a new build. // Documentation is smaller than the source. Upload it first, to minimize // the chances of PUT URLs expiring. (XXX: in the far future, parallelize this) await buildmessage.enterJob("uploading documentation", async function () { await uploadFile(uploadInfo.readmeUrl, readmePath); }); if (buildmessage.jobHasMessages()) { return; } await buildmessage.enterJob("uploading source", async function () { await uploadFile(uploadInfo.uploadUrl, sourceBundleResult.sourceTarball); }); if (buildmessage.jobHasMessages()) { return; } if (! options.doNotPublishBuild) { var bundleResult = await createBuiltPackage( isopack, projectContext.isopackCache, ); if (buildmessage.jobHasMessages()) { return; } } var hashes = { tarballHash: sourceBundleResult.tarballHash, treeHash: sourceBundleResult.treeHash, readmeHash: readmeInfo.hash }; await buildmessage.enterJob("publishing package version", async function () { await callPackageServerBM( conn, 'publishPackageVersion', uploadInfo.uploadToken, hashes); }); if (buildmessage.jobHasMessages()) { return; } if (! options.doNotPublishBuild) { await publishBuiltPackage(conn, isopack, bundleResult); if (buildmessage.jobHasMessages()) { return; } } } return; }; // Call the server to ask if we are authorized to update this release or // package. This is a way to save time before sending data to the server. It // will mostly ignore most errors (just in case we have a flaky network connection or // something) and let the method deal with those. // // If this returns FALSE, then we are NOT authorized. // Otherwise, return true. exports.amIAuthorized = async function (name, conn, isRelease) { var methodName = "amIAuthorized" + (isRelease ? "Release" : "Package"); try { await exports.callPackageServer(conn, methodName, name); } catch (err) { if (err.error === 401) { return false; } // We don't know what this error is. Probably we can't contact the server, // or the like. It would be a pity to fail all operations with the server // just because a preliminary check fails, so return true for now. return true; } return true; };