mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
perf(CLI): Integrate CLI triage into this package
This commit is contained in:
parent
39bdea0750
commit
415bdefca0
@ -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
190
lib/cli/triage.js
Normal 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';
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = { foo: { component: 'foo' } };
|
||||
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = () => {};
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"foo": { "component": "foo" }
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
foo:
|
||||
component: foo
|
||||
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = { component: 'foo' };
|
||||
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = { component: 'foo' };
|
||||
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": "foo"
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
{ "component": "foo" }
|
||||
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -0,0 +1 @@
|
||||
component: foo
|
||||
@ -0,0 +1 @@
|
||||
component: foo
|
||||
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = { service: 'foo', provider: { name: 'foo' }, custom: { component: 'foo' } };
|
||||
@ -0,0 +1,3 @@
|
||||
service: foo
|
||||
provider:
|
||||
name: any
|
||||
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = { component: 'foo' };
|
||||
@ -0,0 +1 @@
|
||||
{ "service": "foo", "provider": { "name": "foo" }, "custom": { "component": "foo" } }
|
||||
@ -0,0 +1,3 @@
|
||||
service: foo
|
||||
provider:
|
||||
name: any
|
||||
@ -0,0 +1 @@
|
||||
{ "component": "foo" }
|
||||
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
foo: { component: 'foo' },
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
service: foo
|
||||
|
||||
provider:
|
||||
name: aws
|
||||
|
||||
custom:
|
||||
component: foo
|
||||
@ -0,0 +1,3 @@
|
||||
service: foo
|
||||
provider:
|
||||
name: any
|
||||
@ -0,0 +1 @@
|
||||
component: foo
|
||||
115
test/unit/lib/cli/triage/index.test.js
Normal file
115
test/unit/lib/cli/triage/index.test.js
Normal 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);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user