Skip to content

Commit

Permalink
refactor(Telemetry): Ensure generation and related utils are sync
Browse files Browse the repository at this point in the history
  • Loading branch information
pgrzesik committed Jul 6, 2021
1 parent cc24bc2 commit b19a7ff
Show file tree
Hide file tree
Showing 18 changed files with 145 additions and 144 deletions.
2 changes: 1 addition & 1 deletion lib/Serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class Serverless {
);
}
if (this.isLocallyInstalled) return;
const localServerlessPath = await resolveLocalServerlessPath();
const localServerlessPath = resolveLocalServerlessPath();
if (!localServerlessPath) return;
if (localServerlessPath === serverlessPath) {
this.isLocallyInstalled = true;
Expand Down
4 changes: 2 additions & 2 deletions lib/classes/PluginManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -615,8 +615,8 @@ class PluginManager {
// TODO: Remove this logic with `v3.0.0` along with removal of `isTelemetryReportedExternally`
// After we are ensured that local fallback from `v3` will always fall back to `v3` local installation
if (!this.serverless.isTelemetryReportedExternally && !isTelemetryDisabled) {
await storeTelemetryLocally(
await generateTelemetryPayload({
storeTelemetryLocally(
generateTelemetryPayload({
...resolveCliInput(),
serviceDir: this.serverless.serviceDir,
configuration: this.serverless.configurationInput,
Expand Down
10 changes: 5 additions & 5 deletions lib/cli/handle-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ module.exports = async (exception, options = {}) => {

// If provided serverless instance is a local fallback, and we're not in context of it
// Pass error handling to this local fallback implementation
if (isInvokedByGlobalInstallation && !(await resolveIsLocallyInstalled())) {
const localServerlessPath = await resolveLocalServerlessPath();
if (isInvokedByGlobalInstallation && !resolveIsLocallyInstalled()) {
const localServerlessPath = resolveLocalServerlessPath();

try {
// Attempt to use error handler from local version
Expand Down Expand Up @@ -135,7 +135,7 @@ module.exports = async (exception, options = {}) => {
const installationModePostfix = await (async () => {
if (isStandaloneExecutable) return ' (standalone)';
if (isLocallyInstalled != null) return isLocallyInstalled ? ' (local)' : '';
return (await resolveIsLocallyInstalled()) ? ' (local)' : '';
return resolveIsLocallyInstalled() ? ' (local)' : '';
})();
consoleLog(
chalk.yellow(` Framework Version: ${slsVersion}${installationModePostfix}`)
Expand Down Expand Up @@ -175,7 +175,7 @@ module.exports = async (exception, options = {}) => {

if (commandSchema) {
// Report only for recognized commands
const telemetryPayload = await generateTelemetryPayload({
const telemetryPayload = generateTelemetryPayload({
command,
options: cliOptions,
commandSchema,
Expand All @@ -192,7 +192,7 @@ module.exports = async (exception, options = {}) => {
if (!isUserError || !exception.code || !isErrorCodeNormative(exception.code)) {
failureReason.location = resolveErrorLocation(exceptionTokens);
}
await storeTelemetryLocally({ ...telemetryPayload, failureReason, outcome: 'failure' });
storeTelemetryLocally({ ...telemetryPayload, failureReason, outcome: 'failure' });
await sendTelemetry();
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/cli/render-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ServerlessError = require('../serverless-error');
const serverlessPath = path.resolve(__dirname, '../..');

module.exports = async () => {
const localServerlessPath = await resolveLocalServerlessPath();
const localServerlessPath = resolveLocalServerlessPath();

if (localServerlessPath) {
// If the version is already local, do not try to fallback for version resolution to avoid falling into the loop
Expand Down
25 changes: 11 additions & 14 deletions lib/cli/resolve-local-serverless-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@

const path = require('path');
const memoizee = require('memoizee');
const resolve = require('ncjsm/resolve');
const resolveSync = require('ncjsm/resolve/sync');

module.exports = memoizee(
async () => {
try {
return path.resolve(
path.dirname((await resolve(process.cwd(), 'serverless')).realPath),
'..'
);
} catch {
return null;
}
},
{ promise: true }
);
// This method should be kept as sync. The reason for it is the fact that
// telemetry generation and persistence needs to be run in sync manner
// and it depends on this function, either directly or indirectly.
module.exports = memoizee(() => {
try {
return path.resolve(path.dirname(resolveSync(process.cwd(), 'serverless').realPath), '..');
} catch {
return null;
}
});
2 changes: 1 addition & 1 deletion lib/plugins/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class Config {
throw new ServerlessError(noSupportErrorMessage, 'AUTO_UPDATE_NOT_SUPPORTED');
}
} else {
if (!(await isNpmGlobalPackage())) {
if (!isNpmGlobalPackage()) {
throw new ServerlessError(noSupportErrorMessage, 'AUTO_UPDATE_NOT_SUPPORTED');
}
if (!(await isNpmPackageWritable())) {
Expand Down
2 changes: 1 addition & 1 deletion lib/utils/eventuallyUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = async (serverless) => {
if (serverless.isLocallyInstalled) return;
if (serverless.isStandaloneExecutable) {
if (process.platform === 'win32') return;
} else if (!(await isNpmGlobalPackage())) {
} else if (!isNpmGlobalPackage()) {
return;
}
const currentVersionData = semver.parse(currentVersion);
Expand Down
7 changes: 5 additions & 2 deletions lib/utils/is-locally-installed.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ const resolveLocalServerlessPath = require('../cli/resolve-local-serverless-path

const serverlessPath = path.resolve(__dirname, '../..');

module.exports = async () => {
const localServerlessPath = await resolveLocalServerlessPath();
// This method should be kept as sync. The reason for it is the fact that
// telemetry generation and persistence needs to be run in sync manner
// and it depends on this function, either directly or indirectly.
module.exports = () => {
const localServerlessPath = resolveLocalServerlessPath();
return serverlessPath === localServerlessPath;
};
30 changes: 15 additions & 15 deletions lib/utils/npmPackage/isGlobal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

const memoizee = require('memoizee');
const path = require('path');
const spawn = require('child-process-ext/spawn');
const { spawnSync } = require('child_process');

const serverlessPackageRoot = path.resolve(__dirname, '../../../');

module.exports = memoizee(
async () => {
const npmPackagesRoot = await (async () => {
try {
return String((await spawn('npm', ['root', '-g'])).stdoutBuffer).trim();
} catch {
return null;
}
})();
if (!npmPackagesRoot) return false;
return path.resolve(npmPackagesRoot, 'serverless') === serverlessPackageRoot;
},
{ type: 'promise' }
);
// This method should be kept as sync. The reason for it is the fact that
// telemetry generation and persistence needs to be run in sync manner
// and it depends on this function, either directly or indirectly.
module.exports = memoizee(() => {
const npmPackagesRoot = (() => {
try {
return String(spawnSync('npm', ['root', '-g']).stdout).trim();
} catch {
return null;
}
})();
if (!npmPackagesRoot) return false;
return path.resolve(npmPackagesRoot, 'serverless') === serverlessPackageRoot;
});
44 changes: 22 additions & 22 deletions lib/utils/telemetry/generatePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

const path = require('path');
const os = require('os');
const fs = require('fs').promises;
const resolve = require('ncjsm/resolve');
const fs = require('fs');
const resolveSync = require('ncjsm/resolve/sync');
const _ = require('lodash');
const isPlainObject = require('type/plain-object/is');
const isObject = require('type/object/is');
Expand All @@ -19,11 +19,11 @@ const AWS = require('aws-sdk');

const configValidationModeValues = new Set(['off', 'warn', 'error']);

const checkIsTabAutocompletionInstalled = async () => {
const checkIsTabAutocompletionInstalled = () => {
try {
return (await fs.readdir(path.resolve(os.homedir(), '.config/tabtab'))).some((filename) =>
filename.startsWith('serverless.')
);
return fs
.readdirSync(path.resolve(os.homedir(), '.config/tabtab'))
.some((filename) => filename.startsWith('serverless.'));
} catch {
return false;
}
Expand Down Expand Up @@ -124,7 +124,9 @@ const getServiceConfig = ({ configuration, options }) => {
return result;
};

module.exports = async ({
// This method is explicitly kept as synchronous. The reason for it being the fact that it needs to
// be executed in such manner due to its use in `process.on('SIGINT')` handler.
module.exports = ({
command,
options,
commandSchema,
Expand Down Expand Up @@ -176,39 +178,37 @@ module.exports = async ({
return userConfig.get('userId');
})();

const isLocallyInstalled = await (async () => {
const isLocallyInstalled = (() => {
if (serverless) {
return serverless.isLocallyInstalled;
}

return resolveIsLocallyInstalled();
})();

const usedVersions = await (async () => {
if (!isLocallyInstalled || (await resolveIsLocallyInstalled())) {
const usedVersions = (() => {
if (!isLocallyInstalled || resolveIsLocallyInstalled()) {
return {
'serverless': require('../../../package').version,
'@serverless/dashboard-plugin': require('@serverless/dashboard-plugin/package').version,
};
}
const localServerlessPath = await resolveLocalServerlessPath();
const localServerlessPath = resolveLocalServerlessPath();
return {
'serverless': require(path.resolve(localServerlessPath, 'package.json')).version,
// Since v2.42.0 it's "@serverless/dashboard-plugin"
'@serverless/dashboard-plugin': await (async () => {
'@serverless/dashboard-plugin': (() => {
try {
return require((
await resolve(localServerlessPath, '@serverless/dashboard-plugin/package')
).realPath).version;
return require(resolveSync(localServerlessPath, '@serverless/dashboard-plugin/package')
.realPath).version;
} catch {
return undefined;
}
})(),
'@serverless/enterprise-plugin': await (async () => {
'@serverless/enterprise-plugin': (() => {
try {
return require((
await resolve(localServerlessPath, '@serverless/enterprise-plugin/package')
).realPath).version;
return require(resolveSync(localServerlessPath, '@serverless/enterprise-plugin/package')
.realPath).version;
} catch {
return undefined;
}
Expand All @@ -230,19 +230,19 @@ module.exports = async ({
},
firstLocalInstallationTimestamp: userConfig.get('meta.created_at'),
frameworkLocalUserId: userConfig.get('frameworkId'),
installationType: await (async () => {
installationType: (() => {
if (isStandalone) {
if (process.platform === 'win32') return 'global:standalone:windows';
return 'global:standalone:other';
}
if (!isLocallyInstalled) {
return (await isNpmGlobal()) ? 'global:npm' : 'global:other';
return isNpmGlobal() ? 'global:npm' : 'global:other';
}
if (serverless && serverless.isInvokedByGlobalInstallation) return 'local:fallback';
return 'local:direct';
})(),
isAutoUpdateEnabled: Boolean(userConfig.get('autoUpdate.enabled')),
isTabAutocompletionInstalled: await checkIsTabAutocompletionInstalled(),
isTabAutocompletionInstalled: checkIsTabAutocompletionInstalled(),
timestamp: Date.now(),
timezone,
triggeredDeprecations: Array.from(triggeredDeprecations),
Expand Down
10 changes: 6 additions & 4 deletions lib/utils/telemetry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,23 @@ async function request(payload, { ids, timeout } = {}) {
return processResponseBody(response, ids, startTime);
}

async function storeLocally(payload, options = {}) {
// This method is explicitly kept as synchronous. The reason for it being the fact that it needs to
// be executed in such manner due to its use in `process.on('SIGINT')` handler.
function storeLocally(payload, options = {}) {
ensurePlainObject(payload);
if (!telemetryUrl) return null;
const isForced = options && options.isForced;
if (isTelemetryDisabled && !isForced) return null;
if (!cacheDirPath) return null;
const id = uuid();

return (async function self() {
return (function self() {
try {
return await fse.writeJson(join(cacheDirPath, id), { payload, timestamp: Date.now() });
return fse.writeJsonSync(join(cacheDirPath, id), { payload, timestamp: Date.now() });
} catch (error) {
if (error.code === 'ENOENT') {
try {
await fse.ensureDir(cacheDirPath);
fse.ensureDirSync(cacheDirPath);
return self();
} catch (ensureDirError) {
logError('Cache dir creation error:', ensureDirError);
Expand Down
62 changes: 30 additions & 32 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,35 @@ const truthyStr = (val) => val && !['0', 'false', 'f', 'n', 'no'].includes(val.t
const { CI, ADBLOCK, SILENT } = process.env;
const isNpmGlobalPackage = require('../lib/utils/npmPackage/isGlobal');

(async () => {
if (!truthyStr(CI) && !truthyStr(ADBLOCK) && !truthyStr(SILENT)) {
const messageTokens = ['Serverless Framework successfully installed!'];

if (isStandaloneExecutable && !isWindows) {
messageTokens.push(
'To start your first project, please open another terminal and run “serverless”.'
);
} else {
messageTokens.push('To start your first project run “serverless”.');
}

if ((isStandaloneExecutable && !isWindows) || (await isNpmGlobalPackage())) {
messageTokens.push('Turn on automatic updates by running “serverless config --autoupdate”.');
}

if (isStandaloneExecutable && !isWindows) {
messageTokens.push('Uninstall at any time by running “serverless uninstall”.');
}

const message = messageTokens.join('\n\n');
process.stdout.write(
`${
isStandaloneExecutable && isWindows
? message
: boxen(chalk.yellow(message), {
padding: 1,
margin: 1,
borderColor: 'yellow',
})
}\n`
if (!truthyStr(CI) && !truthyStr(ADBLOCK) && !truthyStr(SILENT)) {
const messageTokens = ['Serverless Framework successfully installed!'];

if (isStandaloneExecutable && !isWindows) {
messageTokens.push(
'To start your first project, please open another terminal and run “serverless”.'
);
} else {
messageTokens.push('To start your first project run “serverless”.');
}

if ((isStandaloneExecutable && !isWindows) || isNpmGlobalPackage()) {
messageTokens.push('Turn on automatic updates by running “serverless config --autoupdate”.');
}
})();

if (isStandaloneExecutable && !isWindows) {
messageTokens.push('Uninstall at any time by running “serverless uninstall”.');
}

const message = messageTokens.join('\n\n');
process.stdout.write(
`${
isStandaloneExecutable && isWindows
? message
: boxen(chalk.yellow(message), {
padding: 1,
margin: 1,
borderColor: 'yellow',
})
}\n`
);
}

0 comments on commit b19a7ff

Please sign in to comment.