perf(CLI): Integrate CLI triage into this package

This commit is contained in:
Mariusz Nowak 2021-10-22 12:37:26 +02:00 committed by Mariusz Nowak
parent 39bdea0750
commit 415bdefca0
25 changed files with 373 additions and 18 deletions

View File

@ -31,23 +31,20 @@ if (require('../lib/utils/isStandaloneExecutable')) {
}
}
// CLI Triage
(() => {
try {
const componentsV1 = require('@serverless/cli');
const componentsV2 = require('@serverless/components');
(async () => {
const cliName = await require('../lib/cli/triage')();
// Serverless Components v1 CLI (deprecated)
if (componentsV1.runningComponents()) return () => componentsV1.runComponents();
// Serverless Components CLI
if (componentsV2.runningComponents()) return () => componentsV2.runComponents();
} catch (error) {
if (process.env.SLS_DEBUG) {
require('../lib/classes/Error').logWarning(`CLI triage crashed with: ${error.stack}`);
}
switch (cliName) {
case 'serverless':
require('../scripts/serverless');
return;
case '@serverless/components':
require('@serverless/components').runComponents();
return;
case '@serverless/cli':
require('@serverless/cli').runComponents();
return;
default:
throw new Error(`Unrecognized CLI name "${cliName}"`);
}
// Serverless Framework CLI
return () => require('../scripts/serverless');
})()();
})();

190
lib/cli/triage.js Normal file
View File

@ -0,0 +1,190 @@
'use strict';
module.exports = async () => {
const cliArgs = new Set(process.argv.slice(2));
// Unconditionally favor "serverless" for version check
if (cliArgs.has('--version')) return 'serverless';
if (cliArgs.has('-v')) return 'serverless';
// Unconditionally favor "@serverless/components" when component specific command or flag
const componentsCommands = new Set(['registry', 'init', 'publish']);
if (componentsCommands.has(process.argv[2])) return '@serverless/components';
if (cliArgs.has('--help-components')) return '@serverless/components';
if (cliArgs.has('--target')) return '@serverless/components';
// Detect eventual component configuration
const fsp = require('fs').promises;
if (
(
await Promise.all(
['yml', 'yaml', 'json'].map(async (extension) => {
try {
await fsp.access(`serverless.component.${extension}`);
return true;
} catch {
return false;
}
})
)
).some(Boolean)
) {
return '@serverless/components';
}
// Detect eventual service configuration
const configurationExtension =
(
await Promise.all(
['yml', 'yaml', 'json', 'js', 'ts'].map(async (extension) => {
try {
await fsp.access(`serverless.${extension}`);
return extension;
} catch {
return null;
}
})
)
).find(Boolean) || null;
if (configurationExtension) {
// Found top level service configuration, recognize CLI by content
const resolveByObjectConfiguration = (configuration) => {
if (configuration.provider) return 'serverless';
if (configuration.component) return '@serverless/components';
for (const value of Object.values(configuration)) {
if (value.component) return '@serverless/cli';
}
return 'serverless';
};
switch (configurationExtension) {
case 'yml':
case 'yaml': {
const content = await fsp.readFile(`serverless.${configurationExtension}`, 'utf8');
if (content.search(/(?:^|\n)provider\s*:/) !== -1) return 'serverless';
if (content.search(/(?:^|\n)component\s*:/) !== -1) return '@serverless/components';
if (content.search(/\n\s+component\s*:/) !== -1) return '@serverless/cli';
return 'serverless';
}
case 'json': {
const configuration = (() => {
try {
return require(`${process.cwd()}/serverless.json`);
} catch {
return null;
}
})();
if (!configuration) return 'serverless';
return resolveByObjectConfiguration(configuration);
}
case 'js': {
const configuration = (() => {
try {
return require(`${process.cwd()}/serverless.js`);
} catch {
return null;
}
})();
if (!configuration) return 'serverless';
if (typeof configuration === 'function') return '@serverless/cli';
return resolveByObjectConfiguration(configuration);
}
case 'ts':
return 'serverless';
default:
throw new Error(`Unrecognized extension "${configurationExtension}"`);
}
}
// No top level service configuration
const isChinaUser = (() => {
if (process.env.SLS_GEO_LOCATION) return process.env.SLS_GEO_LOCATION === 'cn';
return new Intl.DateTimeFormat('en', { timeZoneName: 'long' })
.format()
.includes('China Standard Time');
})();
// If "sls" or "sls deploy" command and in China, force "@serverless/components"
if (
(process.argv.length === 2 || process.argv[2] === 'deploy') &&
(isChinaUser || process.env.SERVERLESS_PLATFORM_VENDOR === 'tencent')
) {
return '@serverless/components';
}
// Detect eventual component template
const nestedTemplateKeywords = new Set([
'deploy',
'remove',
'info',
'help',
'--help',
'dev',
'logs',
'invoke',
'credentials',
]);
if (!nestedTemplateKeywords.has(process.argv[2])) return 'serverless';
const path = require('path');
const detectSubServiceType = async (subDirname) => {
const subConfigurationExtension =
(
await Promise.all(
['yml', 'yaml', 'json', 'js'].map(async (extension) => {
try {
await fsp.access(path.resolve(subDirname, `serverless.${extension}`));
return extension;
} catch {
return null;
}
})
)
).find(Boolean) || null;
if (!subConfigurationExtension) return null;
switch (subConfigurationExtension) {
case 'yml':
case 'yaml': {
const content = await fsp.readFile(
path.resolve(subDirname, `serverless.${subConfigurationExtension}`),
'utf8'
);
if (content.search(/(?:^|\n)component\s*:/) !== -1) return '@serverless/components';
return 'other';
}
case 'json': {
const configuration = (() => {
try {
return require(`${path.resolve(subDirname)}/serverless.json`);
} catch {
return null;
}
})();
return configuration && configuration.component ? '@serverless/components' : 'other';
}
case 'js': {
const configuration = (() => {
try {
return require(`${path.resolve(subDirname)}/serverless.js`);
} catch {
return null;
}
})();
return configuration && configuration.component ? '@serverless/components' : 'other';
}
default:
throw new Error(`Unrecognized extension "${subConfigurationExtension}"`);
}
};
let hasComponent = false;
for (const subDirname of await fsp.readdir('.')) {
const stats = await fsp.lstat(subDirname);
if (!stats.isDirectory()) continue;
const subServiceType = await detectSubServiceType(subDirname);
if (!subServiceType) continue;
if (subServiceType === '@serverless/components') hasComponent = true;
else return 'serverless';
}
return hasComponent ? '@serverless/components' : 'serverless';
};

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = { foo: { component: 'foo' } };

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = () => {};

View File

@ -0,0 +1,3 @@
{
"foo": { "component": "foo" }
}

View File

@ -0,0 +1,2 @@
foo:
component: foo

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = { component: 'foo' };

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = { component: 'foo' };

View File

@ -0,0 +1,3 @@
{
"component": "foo"
}

View File

@ -0,0 +1 @@
{ "component": "foo" }

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = { service: 'foo', provider: { name: 'foo' }, custom: { component: 'foo' } };

View File

@ -0,0 +1,3 @@
service: foo
provider:
name: any

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = { component: 'foo' };

View File

@ -0,0 +1 @@
{ "service": "foo", "provider": { "name": "foo" }, "custom": { "component": "foo" } }

View File

@ -0,0 +1,3 @@
service: foo
provider:
name: any

View File

@ -0,0 +1 @@
{ "component": "foo" }

View File

@ -0,0 +1,3 @@
module.exports = {
foo: { component: 'foo' },
};

View File

@ -0,0 +1,7 @@
service: foo
provider:
name: aws
custom:
component: foo

View File

@ -0,0 +1,3 @@
service: foo
provider:
name: any

View File

@ -0,0 +1 @@
component: foo

View File

@ -0,0 +1,115 @@
'use strict';
const { expect } = require('chai');
const fs = require('fs');
const overrideCwd = require('process-utils/override-cwd');
const overrideEnv = require('process-utils/override-env');
const overrideArgv = require('process-utils/override-argv');
const path = require('path');
const triage = require('../../../../../lib/cli/triage');
const fixturesDirname = path.resolve(__dirname, 'fixtures');
describe('test/unit/lib/cli/triage/index.test.js', () => {
before(() => overrideEnv({ variables: { SLS_GEO_LOCATION: 'us' } }));
describe('CLI params', () => {
it('should recognize "registry" as "@serverless/components" command', async () =>
overrideArgv({ args: ['sls', 'registry'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
}));
it('should recognize "init" as "@serverless/components" command', async () =>
overrideArgv({ args: ['sls', 'init'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
}));
it('should recognize "publish" as "@serverless/components" command', async () =>
overrideArgv({ args: ['sls', 'publish'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
}));
it('should recognize "--help-components" as "@serverless/components" exclusive flag', async () => {
await overrideArgv({ args: ['sls', '--help-components'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
});
await overrideArgv({ args: ['sls', 'any', '--help-components'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
});
});
it('should recognize "--target" as "@serverless/components" exclusive flag', async () => {
await overrideArgv({ args: ['sls', '--target'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
});
await overrideArgv({ args: ['sls', 'any', '--target'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
});
});
it('should favor "@serverless/components" for bare "sls" command when tencent platform set explicitly', async () =>
overrideEnv({ variables: { SERVERLESS_PLATFORM_VENDOR: 'tencent' } }, async () =>
overrideArgv({ args: ['sls'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
})
));
it('should recognize bare "sls" command in China as "@serverless/components" command', async () =>
overrideEnv({ variables: { SLS_GEO_LOCATION: 'cn' } }, async () =>
overrideArgv({ args: ['sls'] }, async () => {
expect(await triage()).to.equal('@serverless/components');
})
));
it('should unconditionally favor "serverless" for version check', async () =>
overrideEnv(
{ variables: { SERVERLESS_PLATFORM_VENDOR: 'tencent', SLS_GEO_LOCATION: 'cn' } },
async () => {
await overrideArgv({ args: ['sls', '-v'] }, async () => {
expect(await triage()).to.equal('serverless');
});
await overrideArgv({ args: ['sls', '--version'] }, async () => {
expect(await triage()).to.equal('serverless');
});
}
));
it('should favor "serverless" in other cases', async () => {
await overrideArgv({ args: ['sls', 'print'] }, async () => {
expect(await triage()).to.equal('serverless');
});
await overrideArgv({ args: ['sls', 'deploy'] }, async () => {
expect(await triage()).to.equal('serverless');
});
await overrideArgv({ args: ['sls'] }, async () => {
expect(await triage()).to.equal('serverless');
});
await overrideArgv({ args: ['sls', '--help'] }, async () => {
expect(await triage()).to.equal('serverless');
});
});
});
describe('Service configuration', () => {
let restoreArgv;
before(() => {
({ restoreArgv } = overrideArgv({ args: ['sls', 'deploy'] }));
});
after(() => restoreArgv());
for (const cliName of ['serverless', '@serverless/components', '@serverless/cli']) {
for (const extension of fs.readdirSync(path.resolve(fixturesDirname, cliName))) {
for (const fixtureName of fs.readdirSync(
path.resolve(fixturesDirname, cliName, extension)
)) {
const testName = `should recognize "${cliName}" at "${cliName}/${extension}/${fixtureName}"`;
it(testName, async () =>
overrideCwd(
path.resolve(fixturesDirname, cliName, extension, fixtureName),
async () => {
expect(await triage()).to.equal(cliName);
}
)
);
}
}
}
});
});