@fy-stack/cli 0.0.147-300-preview → 0.0.147-alpha.302

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"get-infra.js.d.ts","sourceRoot":"","sources":["../../../src/commands/assets/get-infra.js.ts"],"names":[],"mappings":"AAEA,KAAK,kBAAkB,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,kBAAkB;;;EA4DlD"}
1
+ {"version":3,"file":"get-infra.js.d.ts","sourceRoot":"","sources":["../../../src/commands/assets/get-infra.js.ts"],"names":[],"mappings":"AAEA,KAAK,kBAAkB,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,kBAAkB;;;EAkElD"}
@@ -5,7 +5,8 @@ const get_cdk_json_1 = require("./get-cdk-json");
5
5
  function getInfra(params) {
6
6
  const infraFile = `#!/usr/bin/env node
7
7
  const cdk = require("aws-cdk-lib");
8
- const { FullStackConstruct, AppType } = require("@fy-stack/fullstack-construct");
8
+ const { FullStackConstruct } = require("@fy-stack/fullstack-construct");
9
+ const { AppType } = require("@fy-stack/types");
9
10
  const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts');
10
11
 
11
12
  const env = {
@@ -31,7 +32,12 @@ class AppStack extends cdk.Stack {
31
32
  name: \${appName},
32
33
  ownerArn: props.ownerArn,
33
34
  storage: { retainOnDelete: false },
34
- apps: {}
35
+ lambda: {
36
+ demo: {
37
+ type: AppType.NODE_APP,
38
+ output: './dist'
39
+ }
40
+ }
35
41
  });
36
42
  }
37
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init-app.d.ts","sourceRoot":"","sources":["../../src/commands/init-app.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAavC,wBAAsB,OAAO,CAAC,KAAK,EAAE,YAAY,iBAgKhD"}
1
+ {"version":3,"file":"init-app.d.ts","sourceRoot":"","sources":["../../src/commands/init-app.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAoBvC,wBAAsB,OAAO,CAAC,KAAK,EAAE,YAAY,iBAqThD"}
@@ -4,40 +4,42 @@ exports.initApp = initApp;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs = tslib_1.__importStar(require("node:fs"));
6
6
  const path = tslib_1.__importStar(require("node:path"));
7
+ const client_cloudfront_1 = require("@aws-sdk/client-cloudfront");
7
8
  const client_iam_1 = require("@aws-sdk/client-iam");
9
+ const client_ssm_1 = require("@aws-sdk/client-ssm");
8
10
  const client_sts_1 = require("@aws-sdk/client-sts");
9
11
  const get_infra_js_1 = require("./assets/get-infra.js");
10
12
  const get_yaml_file_1 = require("./assets/get-yaml-file");
11
13
  const iamClient = new client_iam_1.IAMClient();
14
+ const ssmClient = new client_ssm_1.SSMClient();
15
+ const cdnClient = new client_cloudfront_1.CloudFrontClient();
12
16
  async function attachManagedPolicies(roleName, attachedPolices) {
13
17
  for (const policy of attachedPolices ?? []) {
14
18
  await iamClient.send(new client_iam_1.AttachRolePolicyCommand({
15
19
  PolicyArn: policy.PolicyArn,
16
- RoleName: roleName
20
+ RoleName: roleName,
17
21
  }));
18
22
  }
19
23
  }
20
24
  async function initApp(props) {
21
25
  const stsClient = new client_sts_1.STSClient();
22
26
  const user = await stsClient.send(new client_sts_1.GetCallerIdentityCommand());
23
- const username = user.Arn?.split("/").pop();
27
+ const username = user.Arn?.split('/').pop();
24
28
  if (!username)
25
- throw new Error("username not found");
26
- const isRoot = username.endsWith(":root");
29
+ throw new Error('username not found');
30
+ const isRoot = username.endsWith(':root');
27
31
  const workingDir = process.cwd();
28
32
  if (props.githubRepo) {
29
33
  const githubFolderPath = path.join(workingDir, '.github/workflows/');
30
- if (!fs.existsSync(githubFolderPath))
34
+ if (!fs.existsSync(githubFolderPath)) {
31
35
  fs.mkdirSync(githubFolderPath, { recursive: true });
32
- const deployYamlPath = path.join(githubFolderPath, "fy-stack.deploy.yml");
33
- if (fs.existsSync(deployYamlPath))
34
- throw new Error("fy-stack.deploy.yml already exists");
36
+ }
35
37
  const existingRes = await iamClient.send(new client_iam_1.ListOpenIDConnectProvidersCommand());
36
38
  let idProviderArn;
37
39
  const providerUrl = 'token.actions.githubusercontent.com';
38
40
  for (const i in existingRes.OpenIDConnectProviderList ?? []) {
39
41
  const provider = await iamClient.send(new client_iam_1.GetOpenIDConnectProviderCommand({
40
- OpenIDConnectProviderArn: existingRes.OpenIDConnectProviderList?.[i].Arn
42
+ OpenIDConnectProviderArn: existingRes.OpenIDConnectProviderList?.[i].Arn,
41
43
  }));
42
44
  if (provider.Url === providerUrl) {
43
45
  idProviderArn = existingRes.OpenIDConnectProviderList?.[i].Arn;
@@ -47,109 +49,192 @@ async function initApp(props) {
47
49
  if (!idProviderArn) {
48
50
  const idProviderRes = await iamClient.send(new client_iam_1.CreateOpenIDConnectProviderCommand({
49
51
  Url: providerUrl,
50
- ClientIDList: ['sts.amazonaws.com']
52
+ ClientIDList: ['sts.amazonaws.com'],
51
53
  }));
52
- if (!idProviderRes.OpenIDConnectProviderArn)
53
- throw new Error('unable to Github open id provider');
54
54
  idProviderArn = idProviderRes.OpenIDConnectProviderArn;
55
55
  }
56
- const roleRes = await iamClient.send(new client_iam_1.CreateRoleCommand({
57
- RoleName: `${props.app}GithubRole`,
58
- AssumeRolePolicyDocument: JSON.stringify({
59
- Version: "2012-10-17",
60
- Statement: [{
61
- Effect: "Allow",
62
- Principal: { Federated: idProviderArn },
63
- Action: "sts:AssumeRoleWithWebIdentity",
64
- Condition: {
65
- StringEquals: { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
66
- StringLike: { "token.actions.githubusercontent.com:sub": `repo:${props.githubRepo}:*` }
67
- }
68
- }]
69
- }),
70
- }));
71
- if (!roleRes.Role?.Arn || !roleRes.Role.RoleName)
56
+ if (!idProviderArn) {
57
+ throw new Error('unable to create Github OpenID provider');
58
+ }
59
+ const roleName = `${props.app}GithubRole`;
60
+ let roleArn;
61
+ try {
62
+ const existingRole = await iamClient.send(new client_iam_1.GetRoleCommand({ RoleName: roleName }));
63
+ roleArn = existingRole.Role?.Arn;
64
+ }
65
+ catch {
66
+ /** empty */
67
+ }
68
+ if (!roleArn) {
69
+ const roleRes = await iamClient.send(new client_iam_1.CreateRoleCommand({
70
+ RoleName: roleName,
71
+ AssumeRolePolicyDocument: JSON.stringify({
72
+ Version: '2012-10-17',
73
+ Statement: [
74
+ {
75
+ Effect: 'Allow',
76
+ Principal: { Federated: idProviderArn },
77
+ Action: 'sts:AssumeRoleWithWebIdentity',
78
+ Condition: {
79
+ StringEquals: {
80
+ 'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
81
+ },
82
+ StringLike: {
83
+ 'token.actions.githubusercontent.com:sub': `repo:${props.githubRepo}:*`,
84
+ },
85
+ },
86
+ },
87
+ ],
88
+ }),
89
+ }));
90
+ roleArn = roleRes.Role?.Arn;
91
+ }
92
+ if (!roleArn) {
72
93
  throw new Error('unable to Github role');
73
- const roleName = roleRes.Role.RoleName;
94
+ }
74
95
  if (isRoot) {
75
96
  await iamClient.send(new client_iam_1.AttachRolePolicyCommand({
76
97
  PolicyArn: 'arn:aws:iam::aws:policy/AdministratorAccess',
77
- RoleName: roleRes.Role.RoleName
98
+ RoleName: roleName,
78
99
  }));
79
100
  }
80
101
  else {
102
+ // clone policies from logged in aws user to openid user
81
103
  // attach managed polices
82
104
  const attachedPolices = await iamClient.send(new client_iam_1.ListAttachedUserPoliciesCommand({
83
- UserName: username
105
+ UserName: username,
84
106
  }));
85
107
  await attachManagedPolicies(roleName, attachedPolices.AttachedPolicies);
86
108
  // attach inline polices
87
109
  const inlinePolices = await iamClient.send(new client_iam_1.ListUserPoliciesCommand({
88
- UserName: username
110
+ UserName: username,
89
111
  }));
90
112
  for (const policyName of inlinePolices.PolicyNames ?? []) {
91
- const policyDocument = await iamClient.send(new client_iam_1.GetUserPolicyCommand({ UserName: username, PolicyName: policyName }));
113
+ const policyDocument = await iamClient.send(new client_iam_1.GetUserPolicyCommand({
114
+ UserName: username,
115
+ PolicyName: policyName,
116
+ }));
92
117
  if (!policyDocument.PolicyDocument)
93
118
  continue;
94
119
  await iamClient.send(new client_iam_1.PutRolePolicyCommand({
95
120
  PolicyName: policyName,
96
121
  PolicyDocument: decodeURIComponent(policyDocument.PolicyDocument),
97
- RoleName: roleRes.Role.RoleName
122
+ RoleName: roleName,
98
123
  }));
99
124
  }
100
125
  const groups = await iamClient.send(new client_iam_1.ListGroupsForUserCommand({
101
- UserName: username
126
+ UserName: username,
102
127
  }));
103
128
  for (const group of groups.Groups ?? []) {
104
129
  // attach managed polices
105
130
  const attachedPolices = await iamClient.send(new client_iam_1.ListAttachedGroupPoliciesCommand({
106
- GroupName: group.GroupName
131
+ GroupName: group.GroupName,
107
132
  }));
108
133
  await attachManagedPolicies(roleName, attachedPolices.AttachedPolicies);
109
134
  // attach inline polices
110
135
  const inlinePolices = await iamClient.send(new client_iam_1.ListGroupPoliciesCommand({
111
- GroupName: group.GroupName
136
+ GroupName: group.GroupName,
112
137
  }));
113
138
  for (const policyName of inlinePolices.PolicyNames ?? []) {
114
- const policyDocument = await iamClient.send(new client_iam_1.GetUserPolicyCommand({ UserName: username, PolicyName: policyName }));
139
+ const policyDocument = await iamClient.send(new client_iam_1.GetUserPolicyCommand({
140
+ UserName: username,
141
+ PolicyName: policyName,
142
+ }));
115
143
  if (!policyDocument.PolicyDocument)
116
144
  continue;
117
145
  await iamClient.send(new client_iam_1.PutRolePolicyCommand({
118
146
  PolicyName: policyName,
119
147
  PolicyDocument: decodeURIComponent(policyDocument.PolicyDocument),
120
- RoleName: roleRes.Role.RoleName
148
+ RoleName: roleName,
121
149
  }));
122
150
  }
123
151
  }
124
152
  }
125
- const yamlFile = (0, get_yaml_file_1.getYAMLFile)({ ...props, roleArn: roleRes.Role.Arn, region: await stsClient.config.region() });
126
- fs.writeFileSync(deployYamlPath, yamlFile);
153
+ const yamlFile = (0, get_yaml_file_1.getYAMLFile)({
154
+ ...props,
155
+ roleArn,
156
+ region: await stsClient.config.region(),
157
+ });
158
+ const deployYamlPath = path.join(githubFolderPath, 'fy-stack.deploy.yml');
159
+ if (!fs.existsSync(deployYamlPath)) {
160
+ fs.writeFileSync(deployYamlPath, yamlFile);
161
+ }
127
162
  }
128
- const { infraFile, cdkJsonFile } = (0, get_infra_js_1.getInfra)({
129
- app: props.app,
130
- domain: props.domainName
131
- ? `process.env.ENVIRONMENT === "production" ? "${props.domainName}" : \`\${process.env.ENVIRONMENT}.${props.domainName}\``
132
- : undefined
133
- });
163
+ /* Configure image CDN cache policy */
164
+ const imagePolicyParameterName = '/fy-stack/ImagePolicyID';
165
+ const imageCachePolicyConfig = {
166
+ Name: 'AppImagePolicy',
167
+ MinTTL: 0,
168
+ DefaultTTL: 31536000,
169
+ MaxTTL: 31536000,
170
+ ParametersInCacheKeyAndForwardedToOrigin: {
171
+ EnableAcceptEncodingBrotli: true,
172
+ EnableAcceptEncodingGzip: true,
173
+ QueryStringsConfig: { QueryStringBehavior: 'all' },
174
+ HeadersConfig: { HeaderBehavior: 'none' },
175
+ CookiesConfig: { CookieBehavior: 'none' },
176
+ },
177
+ };
178
+ let imagePolicy;
179
+ try {
180
+ const foundParam = await ssmClient.send(new client_ssm_1.GetParameterCommand({ Name: imagePolicyParameterName }));
181
+ if (foundParam.Parameter?.Value) {
182
+ const existingCachePolicy = await cdnClient.send(new client_cloudfront_1.GetCachePolicyCommand({
183
+ Id: foundParam.Parameter.Value,
184
+ }));
185
+ await cdnClient.send(new client_cloudfront_1.UpdateCachePolicyCommand({
186
+ Id: foundParam.Parameter?.Value,
187
+ CachePolicyConfig: imageCachePolicyConfig,
188
+ IfMatch: existingCachePolicy.ETag,
189
+ }));
190
+ imagePolicy = existingCachePolicy.CachePolicy;
191
+ }
192
+ }
193
+ catch {
194
+ /* empty */
195
+ }
196
+ if (!imagePolicy) {
197
+ console.log('image cache policy not found, attempting to create...');
198
+ const cachePolicy = await cdnClient.send(new client_cloudfront_1.CreateCachePolicyCommand({
199
+ CachePolicyConfig: imageCachePolicyConfig,
200
+ }));
201
+ imagePolicy = cachePolicy.CachePolicy;
202
+ }
203
+ if (!imagePolicy) {
204
+ throw new Error('unable to get image cache policy');
205
+ }
206
+ await ssmClient.send(new client_ssm_1.PutParameterCommand({
207
+ Value: imagePolicy.Id,
208
+ Name: imagePolicyParameterName,
209
+ Type: 'String',
210
+ Overwrite: true,
211
+ }));
134
212
  const packageJsonPath = path.join(workingDir, 'package.json');
135
213
  const infraPath = path.join(workingDir, 'infra.js');
136
214
  const cdkPath = path.join(workingDir, 'cdk.json');
137
- if (!fs.existsSync(packageJsonPath))
215
+ if (!fs.existsSync(packageJsonPath)) {
138
216
  throw new Error('unable to find package.json file');
139
- if (fs.existsSync(infraPath))
140
- throw new Error('infra.js file already exists');
141
- const packageJsonFile = JSON.parse(fs.readFileSync(path.join(workingDir, 'package.json'), "utf8"));
142
- packageJsonFile["devDependencies"] = {
143
- ...packageJsonFile["devDependencies"],
144
- "aws-cdk": "^2.1016.1",
145
- "aws-cdk-lib": "^2.198.0",
146
- "@aws-sdk/client-sts": "^3.799.0",
147
- "constructs": "^10.4.2",
148
- "@fy-stack/fullstack-construct": "^0.0.146"
217
+ }
218
+ const packageJsonFile = JSON.parse(fs.readFileSync(path.join(workingDir, 'package.json'), 'utf8'));
219
+ packageJsonFile['devDependencies'] = {
220
+ ...packageJsonFile['devDependencies'],
221
+ 'aws-cdk': packageJsonFile.devDependencies?.['aws-cdk'] ?? '^2.1016.1',
222
+ 'aws-cdk-lib': packageJsonFile.devDependencies?.['aws-cdk-lib'] ?? '^2.229.1',
223
+ '@aws-sdk/client-sts': packageJsonFile.devDependencies?.['@aws-sdk/client-sts'] ?? '^3.799.0',
224
+ constructs: packageJsonFile.devDependencies?.['constructs'] ?? '^10.4.3',
225
+ '@fy-stack/fullstack-construct': packageJsonFile.devDependencies?.['@fy-stack/fullstack-construct'] ??
226
+ '^0.0.147',
227
+ '@fy-stack/types': packageJsonFile.devDependencies?.['@fy-stack/types'] ?? '^0.0.147',
149
228
  };
229
+ const domain = props.domainName
230
+ ? `process.env.ENVIRONMENT === "production" ? "${props.domainName}" : \`\${process.env.ENVIRONMENT}.${props.domainName}\``
231
+ : undefined;
232
+ const { infraFile, cdkJsonFile } = (0, get_infra_js_1.getInfra)({ app: props.app, domain });
233
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonFile, null, 2));
150
234
  if (!fs.existsSync(cdkPath))
151
235
  fs.writeFileSync(cdkPath, cdkJsonFile);
152
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonFile, null, 2));
153
- fs.writeFileSync(infraPath, infraFile);
154
- console.log("App initialized, run npm install to complete");
236
+ if (!fs.existsSync(infraPath)) {
237
+ fs.writeFileSync(infraPath, infraFile);
238
+ }
239
+ console.log('App initialized, run npm install to complete');
155
240
  }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@fy-stack/cli",
3
- "version": "0.0.147-300-preview",
3
+ "version": "0.0.147-alpha.302",
4
4
  "dependencies": {
5
5
  "@aws-sdk/client-iam": "^3.731.1",
6
6
  "@aws-sdk/client-sts": "^3.731.1",
7
+ "@aws-sdk/client-ssm": "^3.731.1",
8
+ "@aws-sdk/client-cloudfront": "^3.731.1",
7
9
  "@inquirer/prompts": "^7.2.3",
8
10
  "commander": "^13.0.0",
9
11
  "tslib": "^2.3.0"