Skip to content

Commit 6974dd8

Browse files
committed
feat: enable --stage option
Signed-off-by: seven <zilisheng1996@gmail.com>
1 parent 3e4ca95 commit 6974dd8

File tree

10 files changed

+165
-15
lines changed

10 files changed

+165
-15
lines changed

package-lock.json

Lines changed: 32 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"chalk": "^5.3.0",
6060
"commander": "^12.1.0",
6161
"i18n": "^0.15.1",
62+
"lodash": "^4.17.21",
6263
"pino": "^9.4.0",
6364
"pino-pretty": "^11.2.2",
6465
"yaml": "^2.5.1"
@@ -67,6 +68,7 @@
6768
"@types/i18n": "^0.13.12",
6869
"@types/jest": "^29.5.13",
6970
"@types/node": "^22.7.4",
71+
"@types/lodash": "^4.17.12",
7072
"@typescript-eslint/eslint-plugin": "^8.8.0",
7173
"@typescript-eslint/parser": "^8.8.0",
7274
"eslint": "^8.57.1",

src/commands/deploy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { constructActionContext, logger } from '../common';
33

44
export const deploy = async (
55
stackName: string,
6-
options: { location: string; parameters: { [key: string]: string } },
6+
options: { location: string; parameters: { [key: string]: string }; stage: string | undefined },
77
) => {
88
const context = constructActionContext({ ...options, stackName });
99
logger.info('Validating yaml...');

src/commands/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,17 @@ program
2626
.command('validate [stackName]')
2727
.description('validate serverless Iac yaml')
2828
.option('-f, --file <path>', 'specify the yaml file')
29-
.action((stackName, options) => {
29+
.option('-s, --stage <stage>', 'specify the stage')
30+
.action((stackName, { file, stage }) => {
3031
logger.debug('log command info');
31-
validate(options.file);
32+
validate(file, stage);
3233
});
3334

3435
program
3536
.command('deploy <stackName>')
3637
.description('deploy serverless Iac yaml')
3738
.option('-f, --file <path>', 'specify the yaml file')
39+
.option('-s, --stage <stage>', 'specify the stage')
3840
.option(
3941
'-p, --parameter <key=value>',
4042
'override parameters',
@@ -45,9 +47,9 @@ program
4547
},
4648
{},
4749
)
48-
.action(async (stackName, options) => {
50+
.action(async (stackName, { file, parameter, stage }) => {
4951
logger.debug('log command info');
50-
await deploy(stackName, { location: options.file, parameters: options.parameter });
52+
await deploy(stackName, { location: file, parameters: parameter, stage });
5153
});
5254

5355
program.parse();

src/commands/validate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { constructActionContext, logger } from '../common';
22
import { parseYaml } from '../stack';
33

4-
export const validate = (location?: string) => {
5-
const context = constructActionContext({ location });
4+
export const validate = (location: string | undefined, stage: string | undefined) => {
5+
const context = constructActionContext({ location, stage });
66
parseYaml(context.iacLocation);
77
logger.info('Yaml is valid! 🎉');
88
logger.debug('Yaml is valid! debug🎉');

src/common/iacHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const replaceReference = <T>(value: T, stage: string): T => {
2121
return ros.Fn.ref(matchVar[1]) as T;
2222
}
2323
if (matchMap?.length) {
24-
return ros.Fn.findInMap('stages', '', matchMap[1]) as T;
24+
return ros.Fn.findInMap('stages', stage, matchMap[1]) as T;
2525
}
2626

2727
if (matchFn?.length) {

tests/commands/deploy.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ describe('unit test for deploy command', () => {
1111
it('should construct valid context and deploy the stack when deploy with valid iac', async () => {
1212
const stackName = 'my-demo-stack';
1313

14-
await deploy(stackName, { location: 'tests/fixtures/serverless-insight.yml', parameters: {} });
14+
await deploy(stackName, {
15+
location: 'tests/fixtures/serverless-insight.yml',
16+
parameters: {},
17+
stage: undefined,
18+
});
1519

1620
expect(mockedDeployStack).toHaveBeenCalledTimes(1);
1721
expect(mockedDeployStack).toHaveBeenCalledWith(stackName, expect.any(Object), defaultContext);

tests/fixtures/deployFixture.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,82 @@ export const oneFcRos = {
208208
},
209209
};
210210

211+
export const oneFcIacWithStage = {
212+
service: 'my-demo-service',
213+
version: '0.0.1',
214+
provider: 'aliyun',
215+
vars: {
216+
region: 'cn-hangzhou',
217+
account_id: 1234567890,
218+
},
219+
stages: {
220+
default: {
221+
node_env: 'default',
222+
},
223+
dev: {
224+
region: '${vars.region}',
225+
account_id: '${vars.account_id}',
226+
node_env: 'develop',
227+
},
228+
},
229+
functions: [
230+
{
231+
key: 'hello_fn',
232+
name: 'hello_fn',
233+
runtime: 'nodejs18',
234+
handler: 'index.handler',
235+
code: 'artifact.zip',
236+
memory: 128,
237+
timeout: 10,
238+
environment: {
239+
NODE_ENV: '${stages.node_env}',
240+
},
241+
},
242+
],
243+
tags: [
244+
{
245+
key: 'owner',
246+
value: 'geek-fun',
247+
},
248+
],
249+
} as ServerlessIac;
250+
251+
export const oneFcWithStageRos = {
252+
Description: 'my-demo-service stack',
253+
Mappings: {
254+
stages: {
255+
default: {
256+
node_env: 'default',
257+
},
258+
dev: {
259+
account_id: { Ref: 'account_id' },
260+
region: { Ref: 'region' },
261+
node_env: 'develop',
262+
},
263+
},
264+
},
265+
Metadata: { 'ALIYUN::ROS::Interface': { TemplateTags: ['Create by ROS CDK'] } },
266+
Parameters: {
267+
account_id: { Default: 1234567890, Type: 'String' },
268+
region: { Default: 'cn-hangzhou', Type: 'String' },
269+
},
270+
ROSTemplateFormatVersion: '2015-09-01',
271+
Resources: {
272+
hello_fn: {
273+
Properties: {
274+
Code: { ZipFile: 'resolved-code' },
275+
EnvironmentVariables: { NODE_ENV: { 'Fn::FindInMap': ['stages', 'default', 'node_env'] } },
276+
FunctionName: 'hello_fn',
277+
Handler: 'index.handler',
278+
MemorySize: 128,
279+
Runtime: 'nodejs18',
280+
Timeout: 10,
281+
},
282+
Type: 'ALIYUN::FC3::Function',
283+
},
284+
},
285+
};
286+
211287
export const defaultContext = {
212288
accessKeyId: 'access key id',
213289
accessKeySecret: 'access key secret',

tests/fixtures/serverless-insight.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ vars:
77
handler: index.handler
88

99
stages:
10+
default:
11+
region: ${vars.region}
12+
node_env: default
1013
dev:
1114
region: ${vars.region}
15+
node_env: development
1216
prod:
1317
region: cn-shanghai
1418

@@ -26,7 +30,7 @@ functions:
2630
memory: 512
2731
timeout: 10
2832
environment:
29-
NODE_ENV: production
33+
NODE_ENV: ${stages.node_env}
3034
TEST_VAR: ${vars.testv}
3135
TEST_VAR_EXTRA: abcds-${vars.testv}-andyou
3236

tests/stack/deploy.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { deployStack } from '../../src/stack';
22
import { ActionContext } from '../../src/types';
33
import {
44
oneFcIac,
5+
oneFcIacWithStage,
56
oneFcOneGatewayIac,
67
oneFcOneGatewayRos,
78
oneFcRos,
9+
oneFcWithStageRos,
810
} from '../fixtures/deployFixture';
11+
import { cloneDeep, set } from 'lodash';
912

1013
const mockedRosStackDeploy = jest.fn();
1114
const mockedResolveCode = jest.fn();
@@ -40,4 +43,36 @@ describe('Unit tests for stack deployment', () => {
4043
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
4144
expect(mockedRosStackDeploy).toHaveBeenCalledWith(stackName, oneFcRos, { stackName });
4245
});
46+
47+
it('should reference to default stage mappings when --stage not provided', async () => {
48+
const options = { stackName: 'my-demo-stack-fc-with-stage-1', stage: 'default' };
49+
mockedRosStackDeploy.mockResolvedValueOnce(options.stackName);
50+
51+
await deployStack(options.stackName, oneFcIacWithStage, options as ActionContext);
52+
53+
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
54+
expect(mockedRosStackDeploy).toHaveBeenCalledWith(
55+
options.stackName,
56+
oneFcWithStageRos,
57+
options,
58+
);
59+
});
60+
61+
it('should reference to specified stage mappings when --stage is provided', async () => {
62+
const options = { stackName: 'my-demo-stack-fc-with-stage-1', stage: 'dev' };
63+
mockedRosStackDeploy.mockResolvedValueOnce(options.stackName);
64+
65+
await deployStack(options.stackName, oneFcIacWithStage, options as ActionContext);
66+
67+
expect(mockedRosStackDeploy).toHaveBeenCalledTimes(1);
68+
expect(mockedRosStackDeploy).toHaveBeenCalledWith(
69+
options.stackName,
70+
set(
71+
cloneDeep(oneFcWithStageRos),
72+
'Resources.hello_fn.Properties.EnvironmentVariables.NODE_ENV.Fn::FindInMap',
73+
['stages', 'dev', 'node_env'],
74+
),
75+
options,
76+
);
77+
});
4378
});

0 commit comments

Comments
 (0)