declapract-typescript-ehmpathy 0.49.3 → 0.49.5

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.
Files changed (40) hide show
  1. package/dist/getVariables.ts +6 -2
  2. package/dist/practices/cicd-app-react-native-expo/best-practice/.github/workflows/.deploy-expo.yml +1 -1
  3. package/dist/practices/cicd-app-react-native-expo/best-practice/.github/workflows/deploy.yml +8 -8
  4. package/dist/practices/cicd-app-react-native-expo/best-practice/.github/workflows/test.yml +3 -3
  5. package/dist/practices/cicd-app-react-native-expo/best-practice/package.json +9 -9
  6. package/dist/practices/cicd-common/best-practice/.github/workflows/.test.yml +4 -4
  7. package/dist/practices/cicd-common/best-practice/.github/workflows/test.yml +1 -1
  8. package/dist/practices/cicd-package/best-practice/.github/workflows/publish.yml +1 -1
  9. package/dist/practices/cicd-service/best-practice/.agent/repo=.this/skills/use.rds.capacity.sh +1 -1
  10. package/dist/practices/cicd-service/best-practice/.declapract.readme.md +1 -1
  11. package/dist/practices/cicd-service/best-practice/.github/workflows/deploy.yml +3 -3
  12. package/dist/practices/cicd-service/best-practice/.github/workflows/provision.yml +9 -9
  13. package/dist/practices/config/bad-practices/configs-contain-wrong-account-id/config/prod.json.declapract.ts +2 -2
  14. package/dist/practices/config/bad-practices/divergent-config-shapes/.declapract.readme.md +1 -1
  15. package/dist/practices/config/bad-practices/divergent-config-shapes/config/{dev.json.declapract.ts → prep.json.declapract.ts} +7 -7
  16. package/dist/practices/config/bad-practices/old-dev-config-location/config/dev.json.declapract.ts +4 -1
  17. package/dist/practices/config/best-practice/config/prep.json +1 -1
  18. package/dist/practices/config/best-practice/config/test.json +1 -1
  19. package/dist/practices/environments/bad-practices/old-dev-scripts/package.json.declapract.ts +57 -0
  20. package/dist/practices/environments/best-practice/.declapract.readme.md +1 -1
  21. package/dist/practices/environments/best-practice/src/utils/environment.ts +2 -2
  22. package/dist/practices/husky/best-practice/.husky/check.timestamps.sh +48 -0
  23. package/dist/practices/husky/best-practice/.husky/pre-commit +2 -0
  24. package/dist/practices/husky/check.timestamps.play.declapract.integration.test.ts +148 -0
  25. package/dist/practices/package-json-order/best-practice/package.json.declapract.ts +11 -11
  26. package/dist/practices/persist-with-rds/best-practice/.declapract.readme.md +21 -1
  27. package/dist/practices/persist-with-rds/best-practice/provision/aws/product/parameter-store.tf +39 -0
  28. package/dist/practices/persist-with-rds/best-practice/provision/schema/deploy.database.sh +9 -9
  29. package/dist/practices/provision-github/best-practice/.declapract.readme.md +7 -0
  30. package/dist/practices/provision-github/best-practice/package.json +1 -1
  31. package/dist/practices/provision-github/best-practice/provision/github.repo/resources.ts +33 -0
  32. package/dist/practices/serverless/best-practice/package.json +2 -2
  33. package/dist/practices/serverless/best-practice/serverless.yml +3 -3
  34. package/dist/practices/serverless/best-practice/serverless.yml.declapract.ts +2 -2
  35. package/dist/practices/terraform-aws/bad-practices/old-dev-env-dir/provision/aws/environments/dev/<star>.declapract.ts +14 -0
  36. package/dist/practices/terraform-aws/best-practice/provision/aws/environments/{dev → prep}/main.tf +2 -2
  37. package/dist/practices/terraform-aws/best-practice/provision/aws/environments/test/main.tf +1 -1
  38. package/package.json +5 -5
  39. /package/dist/practices/config/bad-practices/configs-contain-wrong-account-id/config/{dev.json.declapract.ts → prep.json.declapract.ts} +0 -0
  40. /package/dist/practices/terraform-aws/best-practice/provision/aws/environments/{dev → prep}/versions.tf +0 -0
@@ -10,7 +10,7 @@ export const getServiceVariables = createGetVariables({
10
10
  infrastructureNamespaceId: 'abcde12345',
11
11
  slackWebhookUrl: 'https://...',
12
12
  awsAccountId: {
13
- dev: '123abc',
13
+ prep: '123abc',
14
14
  prod: '456def',
15
15
  },
16
16
  });
@@ -18,9 +18,13 @@ export const getServiceVariables = createGetVariables({
18
18
  export const getRdsVariables = createGetVariables({
19
19
  databaseName: 'awesomethingdb',
20
20
  databaseClusterHost: {
21
- dev: 'awesomesdb.cluster-abc123.us-east-1.rds.amazonaws.com',
21
+ prep: 'awesomesdb.cluster-abc123.us-east-1.rds.amazonaws.com',
22
22
  prod: 'awesomesdb.cluster-def456.us-east-1.rds.amazonaws.com',
23
23
  },
24
+ databaseTunnelHost: {
25
+ prep: 'aws.ssmproxy.awesomesdb.prep',
26
+ prod: 'aws.ssmproxy.awesomesdb.prod',
27
+ },
24
28
  databaseUserName: {
25
29
  serviceUser: 'svc_awesome_thing_user',
26
30
  cicdUser: 'awesomethingdb_cicd',
@@ -5,7 +5,7 @@ on:
5
5
  inputs:
6
6
  stage:
7
7
  type: string
8
- description: "the stage of infrastructure to deploy against (e.g., prod, dev)"
8
+ description: "the stage of infrastructure to deploy against (e.g., prod, prep)"
9
9
  required: true
10
10
  build:
11
11
  type: string
@@ -13,10 +13,10 @@ on:
13
13
  description: 'which stage do you want to deploy to?'
14
14
  type: choice
15
15
  options:
16
- - dev
16
+ - prep
17
17
  - prod
18
18
  required: true
19
- default: 'dev'
19
+ default: 'prep'
20
20
  thoroughly:
21
21
  description: 'should we run tests before this deployment?'
22
22
  type: choice
@@ -36,22 +36,22 @@ jobs:
36
36
  if: github.event_name != 'workflow_dispatch' || github.event.inputs.thoroughly == 'true'
37
37
  with:
38
38
  creds-aws-region: us-east-1
39
- creds-aws-role-arn: arn:aws:iam::@declapract{variable.awsAccountId.dev}:role/@declapract{variable.projectName}-github-actions-dev
39
+ creds-aws-role-arn: arn:aws:iam::@declapract{variable.awsAccountId.prep}:role/@declapract{variable.projectName}-github-actions-prep
40
40
  secrets: inherit # keyrack firewall in .test.yml filters to declared keys
41
41
 
42
- dev:
42
+ prep:
43
43
  uses: ./.github/workflows/.deploy-expo.yml
44
44
  needs: [test]
45
45
  # dont deploy on merge to main by default, since builds are expensive
46
46
  if: |
47
- ((github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' && false) || github.event.inputs.stage == 'dev') &&
47
+ ((github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' && false) || github.event.inputs.stage == 'prep') &&
48
48
  always() && (needs.test.result == 'success' || needs.test.result == 'skipped')
49
49
  with:
50
- stage: dev
50
+ stage: prep
51
51
  build: development
52
- github-environment: dev
52
+ github-environment: prep
53
53
  creds-aws-region: us-east-1
54
- creds-aws-role-arn: arn:aws:iam::@declapract{variable.awsAccountId.dev}:role/@declapract{variable.projectName}-github-actions-dev
54
+ creds-aws-role-arn: arn:aws:iam::@declapract{variable.awsAccountId.prep}:role/@declapract{variable.projectName}-github-actions-prep
55
55
  secrets:
56
56
  expo-token: ${{ secrets.EXPO_TOKEN }}
57
57
 
@@ -17,7 +17,7 @@ jobs:
17
17
  uses: ./.github/workflows/.test.yml
18
18
  with:
19
19
  aws-region: us-east-1
20
- aws-account-id: '@declapract{variable.awsAccountId.dev}'
20
+ aws-account-id: '@declapract{variable.awsAccountId.prep}'
21
21
  secrets:
22
- aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
23
- aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
22
+ aws-access-key-id: ${{ secrets.PREP_AWS_ACCESS_KEY_ID }}
23
+ aws-secret-access-key: ${{ secrets.PREP_AWS_SECRET_ACCESS_KEY }}
@@ -12,28 +12,28 @@
12
12
  },
13
13
  "scripts": {
14
14
  "start:hot:proxy": "npx local-ssl-proxy --source 3447 --target 8081",
15
- "start:hot:dev": "STAGE=dev npx expo start --clear --dev-client",
16
- "start:hot:dev:link": "echo '🚀 https://localhost.@declapract{variable.app.web.domain.dev}:@declapract{variable.app.web.port.hot}'",
17
- "start:hot:dev:proxy": "npm run start:hot:dev:link && npm run start:hot:proxy",
15
+ "start:hot:prep": "STAGE=prep npx expo start --clear --dev-client",
16
+ "start:hot:prep:link": "echo '🚀 https://localhost.@declapract{variable.app.web.domain.prep}:@declapract{variable.app.web.port.hot}'",
17
+ "start:hot:prep:proxy": "npm run start:hot:prep:link && npm run start:hot:proxy",
18
18
  "start:hot:prod": "STAGE=prod npx expo start --clear --dev-client",
19
19
  "start:hot:prod:link": "echo '🚀 https://localhost.@declapract{variable.app.web.domain.prod}:@declapract{variable.app.web.port.hot}'",
20
20
  "start:hot:prod:proxy": "npm run start:hot:prod:link && npm run start:hot:proxy",
21
21
  "start:cold:proxy": "npx local-ssl-proxy --source 3449 --target 3000",
22
- "start:cold:dev": "npm run build:web && npx serve dist --single",
23
- "start:cold:dev:link": "echo '🚀 https://localhost.@declapract{variable.app.web.domain.dev}:@declapract{variable.app.web.port.cold}'",
24
- "start:cold:dev:proxy": "echo npm run start:cold:dev:link && npm run start:cold:proxy",
22
+ "start:cold:prep": "npm run build:web && npx serve dist --single",
23
+ "start:cold:prep:link": "echo '🚀 https://localhost.@declapract{variable.app.web.domain.prep}:@declapract{variable.app.web.port.cold}'",
24
+ "start:cold:prep:proxy": "echo npm run start:cold:prep:link && npm run start:cold:proxy",
25
25
  "start:cold:prod": "npm run build:web && npx serve dist --single",
26
26
  "start:cold:prod:link": "echo '🚀 https://localhost.@declapract{variable.app.web.domain.prod}:@declapract{variable.app.web.port.cold}'",
27
27
  "register:device:apple:info": "echo 'learn more here https://docs.expo.dev/build/internal-distribution/'",
28
28
  "register:device:apple:inclusions:rebuild:prod": "npx eas build --platform ios --clear-cache",
29
- "register:device:apple:inclusions:rebuild:dev": "npx eas build --profile development --platform ios --clear-cache",
29
+ "register:device:apple:inclusions:rebuild:prep": "npx eas build --profile development --platform ios --clear-cache",
30
30
  "register:device:apple:add": "echo 'register an apple device to allow it to install internal builds' && eas device:create && npm run register:device:apple:inclusions:rebuild",
31
31
  "register:device:apple:list": "eas device:list",
32
32
  "register:device:apple:delete": "eas device:delete",
33
33
  "register:device:apple:rename": "eas device:rename",
34
34
  "register:profile:apple:list": "echo 'see list here https://developer.apple.com/account/resources/profiles/list'",
35
- "build:dev:ios": "eas build --profile development --platform ios --non-interactive",
36
- "build:dev:android": "eas build --profile development --platform android --non-interactive",
35
+ "build:prep:ios": "eas build --profile development --platform ios --non-interactive",
36
+ "build:prep:android": "eas build --profile development --platform android --non-interactive",
37
37
  "build:web": "npx expo export --platform web",
38
38
  "deploy:prod:ios:metadata": "eas metadata:push",
39
39
  "deploy:prod": "echo 'use github actions'",
@@ -226,8 +226,8 @@ jobs:
226
226
  - name: start:testdb
227
227
  run: npm run start:testdb --if-present
228
228
 
229
- - name: start:livedb:dev
230
- run: npm run start:livedb:dev --if-present
229
+ - name: start:livedb:prep
230
+ run: npm run start:livedb:prep --if-present
231
231
 
232
232
  - name: build
233
233
  run: npm run build
@@ -310,8 +310,8 @@ jobs:
310
310
  - name: start:testdb
311
311
  run: npm run start:testdb --if-present
312
312
 
313
- - name: start:livedb:dev
314
- run: npm run start:livedb:dev --if-present
313
+ - name: start:livedb:prep
314
+ run: npm run start:livedb:prep --if-present
315
315
 
316
316
  - name: build
317
317
  run: npm run build
@@ -21,5 +21,5 @@ jobs:
21
21
  uses: ./.github/workflows/.test.yml
22
22
  with:
23
23
  creds-aws-region: us-east-1
24
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
24
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
25
25
  secrets: inherit # keyrack firewall in .test.yml filters to declared keys
@@ -18,7 +18,7 @@ jobs:
18
18
  uses: ./.github/workflows/.test.yml
19
19
  with:
20
20
  creds-aws-region: us-east-1
21
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
21
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
22
22
  secrets: inherit # keyrack firewall in .test.yml filters to declared keys
23
23
 
24
24
  publish:
@@ -30,7 +30,7 @@ set -eo pipefail
30
30
 
31
31
  set -u
32
32
 
33
- # ensure the dev tunnel is awake
33
+ # ensure the prep tunnel is awake
34
34
  .agent/repo=.this/skills/use.vpc.tunnel.ts
35
35
 
36
36
  # ping until available
@@ -1,4 +1,4 @@
1
1
  flows:
2
2
  - test on every commit
3
- - deploy to dev every commit on master
3
+ - deploy to prep every commit on master
4
4
  - deploy to prod every v* tagged commit on master
@@ -40,7 +40,7 @@ jobs:
40
40
  if: github.event_name != 'workflow_dispatch' || github.event.inputs.thoroughly == 'true'
41
41
  with:
42
42
  creds-aws-region: us-east-1
43
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
43
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
44
44
  secrets: inherit # keyrack firewall in .test.yml filters to declared keys
45
45
 
46
46
  dev:
@@ -51,9 +51,9 @@ jobs:
51
51
  always() && (needs.test.result == 'success' || needs.test.result == 'skipped')
52
52
  with:
53
53
  stage: dev
54
- github-environment: dev
54
+ github-environment: prep # github environment stays as prep (where secrets/vars live)
55
55
  creds-aws-region: us-east-1
56
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }}
56
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }}
57
57
 
58
58
  prod:
59
59
  uses: ./.github/workflows/.deploy-sls.yml
@@ -19,17 +19,17 @@ jobs:
19
19
  uses: ./.github/workflows/.terraform.yml
20
20
  with:
21
21
  working-directory: provision/aws/environments/test
22
- github-environment: dev
22
+ github-environment: prep
23
23
  creds-aws-region: us-east-1
24
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }}
24
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }}
25
25
 
26
- aws-dev:
26
+ aws-prep:
27
27
  uses: ./.github/workflows/.terraform.yml
28
28
  with:
29
- working-directory: provision/aws/environments/dev
30
- github-environment: dev
29
+ working-directory: provision/aws/environments/prep
30
+ github-environment: prep
31
31
  creds-aws-region: us-east-1
32
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }}
32
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }}
33
33
 
34
34
  aws-prod:
35
35
  uses: ./.github/workflows/.terraform.yml
@@ -50,13 +50,13 @@ jobs:
50
50
  secrets:
51
51
  creds-github-app-private-key: ${{ secrets.DECLASTRUCT_GITHUB_CONFORMER_APP_PRIVATE_KEY }}
52
52
 
53
- sql-schema-dev:
53
+ sql-schema-prep:
54
54
  uses: ./.github/workflows/.sql-schema-control.yml
55
55
  with:
56
56
  stage: prep
57
- github-environment: dev
57
+ github-environment: prep
58
58
  creds-aws-region: us-east-1
59
- creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }}
59
+ creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_PREP_OIDC_ROLE_ARN }}
60
60
 
61
61
  sql-schema-prod:
62
62
  uses: ./.github/workflows/.sql-schema-control.yml
@@ -4,6 +4,6 @@ import { getServiceVariables } from '../../../../../getVariables';
4
4
 
5
5
  export const check: FileCheckFunction = (contents, context) => {
6
6
  const { awsAccountId } = getServiceVariables(context);
7
- if (!contents?.includes(awsAccountId.dev))
8
- throw new Error('does not contain aws dev account id');
7
+ if (!contents?.includes(awsAccountId.prep))
8
+ throw new Error('does not contain aws prep account id');
9
9
  };
@@ -1,6 +1,6 @@
1
1
  the following places should have the shape of the config defined identically:
2
2
  - `config/test.json`
3
- - `config/dev.json`
3
+ - `config/prep.json`
4
4
  - `config/prod.json`
5
5
 
6
6
  this check complains if one of them does not look like the others
@@ -16,18 +16,18 @@ export const check: FileCheckFunction = async (contents, context) => {
16
16
  const flattenedTestConfigKeys = Object.keys(flatten(testConfigObject));
17
17
 
18
18
  // grab the found keys
19
- const devConfigObject = JSON.parse(contents);
20
- const flattenedDevConfigKeys = Object.keys(flatten(devConfigObject));
19
+ const prepConfigObject = JSON.parse(contents);
20
+ const flattenedPrepConfigKeys = Object.keys(flatten(prepConfigObject));
21
21
 
22
22
  // determine which keys are not matched
23
- const keysInTestButNotInDev = flattenedTestConfigKeys.filter(
24
- (key) => !flattenedDevConfigKeys.includes(key),
23
+ const keysInTestButNotInPrep = flattenedTestConfigKeys.filter(
24
+ (key) => !flattenedPrepConfigKeys.includes(key),
25
25
  );
26
- const keysInDevButNotInTest = flattenedDevConfigKeys.filter(
26
+ const keysInPrepButNotInTest = flattenedPrepConfigKeys.filter(
27
27
  (key) => !flattenedTestConfigKeys.includes(key),
28
28
  );
29
- if (keysInTestButNotInDev.length) return; // matches bad practice
30
- if (keysInDevButNotInTest.length) return; // matches bad practice
29
+ if (keysInTestButNotInPrep.length) return; // matches bad practice
30
+ if (keysInPrepButNotInTest.length) return; // matches bad practice
31
31
 
32
32
  // otherwise, does not match a bad practice
33
33
  throw new Error('its fine');
@@ -4,9 +4,12 @@ export const check = FileCheckType.EXISTS;
4
4
 
5
5
  export const fix: FileFixFunction = (contents, context) => {
6
6
  // move config/dev.json → config/prep.json and update access: dev → prep
7
+ // also update .dev hostnames to .prep (e.g., bastion.dev.example.com → bastion.prep.example.com)
7
8
  let fixed = contents;
8
9
  if (fixed) {
9
- fixed = fixed.replace(/"access":\s*"dev"/, '"access": "prep"');
10
+ fixed = fixed
11
+ .replace(/\.dev\b/g, '.prep')
12
+ .replace(/"access":\s*"dev"/, '"access": "prep"');
10
13
  }
11
14
  return {
12
15
  contents: fixed ?? null,
@@ -5,7 +5,7 @@
5
5
  "access": "prep"
6
6
  },
7
7
  "aws": {
8
- "account": "@declapract{variable.awsAccountId.dev}",
8
+ "account": "@declapract{variable.awsAccountId.prep}",
9
9
  "namespace": "@declapract{variable.infrastructureNamespaceId}"
10
10
  }
11
11
  }
@@ -5,7 +5,7 @@
5
5
  "access": "test"
6
6
  },
7
7
  "aws": {
8
- "account": "@declapract{variable.awsAccountId.dev}",
8
+ "account": "@declapract{variable.awsAccountId.prep}",
9
9
  "namespace": "@declapract{variable.infrastructureNamespaceId}"
10
10
  }
11
11
  }
@@ -0,0 +1,57 @@
1
+ import type { FileCheckFunction, FileFixFunction } from 'declapract';
2
+
3
+ /**
4
+ * .what = detects package.json scripts with `:dev` as a segment
5
+ * .why = standardizing on `prep` over `dev` for pre-production environment naming
6
+ *
7
+ * matches both `:dev` at end (deploy:dev) and `:dev:` in middle (build:dev:ios)
8
+ */
9
+ export const check: FileCheckFunction = (contents) => {
10
+ if (!contents) throw new Error('fine, does not match bad practice');
11
+ const parsedContents = JSON.parse(contents);
12
+ const scripts = parsedContents.scripts ?? {};
13
+
14
+ // check if any script name contains :dev as a segment (either :dev at end or :dev: in middle)
15
+ const devScripts = Object.keys(scripts).filter(
16
+ (key) => key.endsWith(':dev') || key.includes(':dev:'),
17
+ );
18
+ if (devScripts.length > 0) return; // matches bad practice
19
+
20
+ throw new Error('fine, no scripts with :dev segment');
21
+ };
22
+
23
+ /**
24
+ * .what = renames scripts from `:dev` segment to `:prep` segment
25
+ * .why = standardizing on `prep` over `dev` for pre-production environment naming
26
+ *
27
+ * handles both `:dev` at end (deploy:dev) and `:dev:` in middle (build:dev:ios)
28
+ *
29
+ * .note = only renames script NAMES, not VALUES. script values may still reference
30
+ * `--stage dev` for infrastructure naming (cloudformation stacks stay named
31
+ * `$service-dev`). the serverless package.json handles this via SLS_STAGE
32
+ * mapping. see brief: define.infrastructure-dev-vs-application-prep.md
33
+ */
34
+ export const fix: FileFixFunction = (contents) => {
35
+ if (!contents) return {};
36
+ const parsedContents = JSON.parse(contents);
37
+ const scripts = parsedContents.scripts ?? {};
38
+
39
+ // find scripts with :dev segment and create renamed versions
40
+ const updatedScripts = { ...scripts };
41
+ for (const [key, value] of Object.entries(scripts)) {
42
+ if (key.endsWith(':dev') || key.includes(':dev:')) {
43
+ // replace :dev: in middle and :dev at end with :prep
44
+ const newKey = key.replace(/:dev:/g, ':prep:').replace(/:dev$/, ':prep');
45
+ updatedScripts[newKey] = value; // add :prep version
46
+ updatedScripts[key] = undefined; // remove :dev version
47
+ }
48
+ }
49
+
50
+ return {
51
+ contents: JSON.stringify(
52
+ { ...parsedContents, scripts: updatedScripts },
53
+ null,
54
+ 2,
55
+ ),
56
+ };
57
+ };
@@ -4,7 +4,7 @@ a critical part of an application's environment is which stage it is running in
4
4
 
5
5
  ehmpathy recognizes three standard stages in which applications run throughout their development cycle
6
6
  - `prod` stage - a deployed production stack in which application that the business depends on run, affecting real customers
7
- - `dev` stage - a deployed developer-only stack in which applications can run and interact with each other for testing
7
+ - `prep` stage - a deployed pre-production stack in which applications can run and interact with each other for testing
8
8
  - `test` stage - an ephemeral, primarily local, developer-only stack in which developers run local code against for testing
9
9
 
10
10
  this information is expected to be defined in the runtime environment through the following environmental variables
@@ -4,5 +4,5 @@ export { getEnvironment };
4
4
 
5
5
  export const envStatic = getEnvironment.static();
6
6
 
7
- export const stage = envStatic.access === 'prep' ? 'dev' : envStatic.access;
8
- export const serviceClientStage = stage === 'prod' ? 'prod' : 'dev';
7
+ export const stage = envStatic.access;
8
+ export const serviceClientStage = stage === 'prod' ? 'prod' : 'prep';
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+
3
+ # .what = block commit of files that embed a timestamp (T?HH:MM:SS{tz})
4
+ # .why = timestamps in snapshots permadrift — they change every run, so the
5
+ # snapshot can never be "correct" twice; mask them to keep it stable
6
+ # .how = scan staged files (except .ts/.sh) for the pattern; halt with a
7
+ # constraint error (exit 2) if any are found
8
+
9
+ # fail fast on errors and unbound vars (safe here: scan pipelines end in `head`
10
+ # so a no-match grep does not abort, and all vars are assigned before use)
11
+ set -eu
12
+
13
+ # the timestamp pattern: optional lead T, HH:MM:SS, optional timezone (Z, ±HH:MM, ±HHMM)
14
+ TIMESTAMP_PATTERN='T?[0-9][0-9]:[0-9][0-9]:[0-9][0-9](Z|[+-][0-9][0-9]:?[0-9][0-9])?'
15
+
16
+ # collect staged files (added/copied/modified); skip deletions
17
+ staged=$(git diff --cached --name-only --diff-filter=ACM)
18
+
19
+ # scan each staged file for a raw timestamp
20
+ # note: the here-doc feeds the loop in the current shell (no pipe subshell),
21
+ # so `exit 2` halts the commit directly and a clean pass falls through
22
+ while IFS= read -r file; do
23
+ # skip empty lines
24
+ [ -n "$file" ] || continue
25
+
26
+ # skip exempt code files (.ts and .sh legitimately carry time logic)
27
+ case "$file" in
28
+ *.ts | *.sh) continue ;;
29
+ esac
30
+
31
+ # read the staged content; fail loud if git cannot read the staged blob
32
+ # (2>&1 captures git's error into the var so the halt message can surface it)
33
+ if ! content=$(git show ":$file" 2>&1); then
34
+ printf '💥 check.timestamps: failed to read staged content for %s\n' "$file" >&2
35
+ printf ' └─ %s\n' "$content" >&2
36
+ exit 1
37
+ fi
38
+
39
+ # halt if the staged content embeds a raw timestamp
40
+ match=$(printf '%s\n' "$content" | grep -nIE "$TIMESTAMP_PATTERN" | head -n 1)
41
+ if [ -n "$match" ]; then
42
+ printf '✋ timestamps are forbidden in snapshots. mask them to prevent permadrift\n'
43
+ printf ' └─ %s:%s\n' "$file" "$match"
44
+ exit 2
45
+ fi
46
+ done <<EOF
47
+ $staged
48
+ EOF
@@ -6,3 +6,5 @@ fi
6
6
  . "$(dirname -- "$0")/_/husky.sh"
7
7
 
8
8
  . "$(dirname -- "$0")/check.yalc.sh"
9
+
10
+ . "$(dirname -- "$0")/check.timestamps.sh"
@@ -0,0 +1,148 @@
1
+ import { execFileSync, spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ import { given, then, when } from 'test-fns';
7
+
8
+ /**
9
+ * .what = integration test for the check.timestamps husky guard
10
+ * .why = proves the guard halts commits of files that embed a timestamp
11
+ * (except .ts/.sh) with exit 2, and allows clean/exempt files with exit 0
12
+ * .how = build a temp git repo, stage fixtures, spawn the guard, assert exit code
13
+ */
14
+
15
+ // the guard under test (the distributed best-practice template)
16
+ const guardPath = path.join(
17
+ __dirname,
18
+ 'best-practice/.husky/check.timestamps.sh',
19
+ );
20
+
21
+ // build a fresh temp git repo with the given files staged
22
+ const genStagedRepo = (files: Record<string, string>): string => {
23
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'check-timestamps-'));
24
+ execFileSync('git', ['init', '-q'], { cwd: dir });
25
+ for (const [name, content] of Object.entries(files)) {
26
+ const full = path.join(dir, name);
27
+ fs.mkdirSync(path.dirname(full), { recursive: true });
28
+ fs.writeFileSync(full, content);
29
+ execFileSync('git', ['add', '--', name], { cwd: dir });
30
+ }
31
+ return dir;
32
+ };
33
+
34
+ // spawn the guard against a repo; return its exit code + stdout
35
+ const runGuard = (dir: string): { code: number | null; stdout: string } => {
36
+ const res = spawnSync('bash', [guardPath], { cwd: dir, encoding: 'utf-8' });
37
+ return { code: res.status, stdout: res.stdout };
38
+ };
39
+
40
+ describe('check.timestamps guard', () => {
41
+ given('[case1] a non-code file with an iso timestamp', () => {
42
+ when('[t0] the guard runs', () => {
43
+ const dir = genStagedRepo({
44
+ 'snapshot.json': '{ "createdAt": "2026-06-25T14:30:00Z" }\n',
45
+ });
46
+ const result = runGuard(dir);
47
+
48
+ then('it halts with exit 2', () => {
49
+ expect(result.code).toEqual(2);
50
+ });
51
+ then('it prints the verbatim constraint message', () => {
52
+ expect(result.stdout).toContain(
53
+ 'timestamps are forbidden in snapshots. mask them to prevent permadrift',
54
+ );
55
+ });
56
+ then('it names the matched file', () => {
57
+ expect(result.stdout).toContain('snapshot.json');
58
+ });
59
+ then('it renders the matched file:line:content detail line', () => {
60
+ // locks the exact halt-output shape: tree-prefixed detail line
61
+ expect(result.stdout).toContain(
62
+ ' └─ snapshot.json:1:{ "createdAt": "2026-06-25T14:30:00Z" }',
63
+ );
64
+ });
65
+ });
66
+ });
67
+
68
+ given('[case2] a non-code file with a bare time (no lead T)', () => {
69
+ when('[t0] the guard runs', () => {
70
+ const dir = genStagedRepo({ 'doc.md': 'released at 14:30:00 today\n' });
71
+ const result = runGuard(dir);
72
+
73
+ then('it halts with exit 2 (optional T prefix matches bare time)', () => {
74
+ expect(result.code).toEqual(2);
75
+ });
76
+ });
77
+ });
78
+
79
+ given('[case3] a .ts file with a timestamp', () => {
80
+ when('[t0] the guard runs', () => {
81
+ const dir = genStagedRepo({ 'time.ts': "const t = '14:30:00';\n" });
82
+ const result = runGuard(dir);
83
+
84
+ then('it allows the commit with exit 0 (.ts is exempt)', () => {
85
+ expect(result.code).toEqual(0);
86
+ });
87
+ });
88
+ });
89
+
90
+ given('[case4] a .sh file with a timestamp', () => {
91
+ when('[t0] the guard runs', () => {
92
+ const dir = genStagedRepo({ 'log.sh': "echo 'at 14:30:00Z'\n" });
93
+ const result = runGuard(dir);
94
+
95
+ then('it allows the commit with exit 0 (.sh is exempt)', () => {
96
+ expect(result.code).toEqual(0);
97
+ });
98
+ });
99
+ });
100
+
101
+ given('[case5] a clean non-code file with no timestamp', () => {
102
+ when('[t0] the guard runs', () => {
103
+ const dir = genStagedRepo({ 'notes.md': 'hello world, no times here\n' });
104
+ const result = runGuard(dir);
105
+
106
+ then('it allows the commit with exit 0', () => {
107
+ expect(result.code).toEqual(0);
108
+ });
109
+ });
110
+ });
111
+
112
+ given('[case6] a non-code file with only a date (no time)', () => {
113
+ when('[t0] the guard runs', () => {
114
+ const dir = genStagedRepo({ 'dated.md': 'shipped on 2026-06-25\n' });
115
+ const result = runGuard(dir);
116
+
117
+ then('it allows the commit with exit 0 (date alone does not match)', () => {
118
+ expect(result.code).toEqual(0);
119
+ });
120
+ });
121
+ });
122
+
123
+ given('[case7] a non-code file with a timezone-offset timestamp', () => {
124
+ when('[t0] the guard runs', () => {
125
+ const dir = genStagedRepo({
126
+ 'rec.yml': 'when: 2026-06-25T14:30:00+05:00\n',
127
+ });
128
+ const result = runGuard(dir);
129
+
130
+ then('it halts with exit 2 (offset tz form matches)', () => {
131
+ expect(result.code).toEqual(2);
132
+ });
133
+ });
134
+ });
135
+
136
+ given('[case8] a snapshot with a masked timestamp placeholder', () => {
137
+ when('[t0] the guard runs', () => {
138
+ const dir = genStagedRepo({
139
+ 'render.test.ts.snap': '{ "createdAt": "[timestamp]" }\n',
140
+ });
141
+ const result = runGuard(dir);
142
+
143
+ then('it allows the commit with exit 0 (mask remedy is committable)', () => {
144
+ expect(result.code).toEqual(0);
145
+ });
146
+ });
147
+ });
148
+ });
@@ -53,7 +53,7 @@ export const desiredRelativeKeyOrder = {
53
53
  'provision:testdb:docker:down',
54
54
  'provision:testdb',
55
55
  'start:testdb',
56
- 'start:livedb:dev',
56
+ 'start:livedb:prep',
57
57
  'test:auth',
58
58
  'test:commits',
59
59
  'test:types',
@@ -82,34 +82,34 @@ export const desiredRelativeKeyOrder = {
82
82
  'deploy:prune',
83
83
  'deploy:release',
84
84
  'deploy:send-notification',
85
- 'deploy:dev',
85
+ 'deploy:prep',
86
86
  'deploy:prod',
87
87
  'deploy',
88
88
 
89
89
  // from app-react-native-expo
90
90
  'start:hot:proxy',
91
- 'start:hot:dev',
92
- 'start:hot:dev:link',
93
- 'start:hot:dev:proxy',
91
+ 'start:hot:prep',
92
+ 'start:hot:prep:link',
93
+ 'start:hot:prep:proxy',
94
94
  'start:hot:prod',
95
95
  'start:hot:prod:link',
96
96
  'start:hot:prod:proxy',
97
97
  'start:cold:proxy',
98
- 'start:cold:dev',
99
- 'start:cold:dev:link',
100
- 'start:cold:dev:proxy',
98
+ 'start:cold:prep',
99
+ 'start:cold:prep:link',
100
+ 'start:cold:prep:proxy',
101
101
  'start:cold:prod',
102
102
  'start:cold:prod:link',
103
103
  'register:device:apple:info',
104
104
  'register:device:apple:inclusions:rebuild:prod',
105
- 'register:device:apple:inclusions:rebuild:dev',
105
+ 'register:device:apple:inclusions:rebuild:prep',
106
106
  'register:device:apple:add',
107
107
  'register:device:apple:list',
108
108
  'register:device:apple:delete',
109
109
  'register:device:apple:rename',
110
110
  'register:profile:apple:list',
111
- 'build:dev:ios',
112
- 'build:dev:android',
111
+ 'build:prep:ios',
112
+ 'build:prep:android',
113
113
  'build:web',
114
114
  'deploy:prod:ios:metadata',
115
115
  'open:store:android',
@@ -1,6 +1,26 @@
1
1
  we use:
2
2
  - postgres database
3
- - provisioned by aws rds aurora when in dev and prod env
3
+ - provisioned by aws rds aurora when in prep and prod env
4
4
  - provisioned by docker when in test env
5
5
  - sql-schema-control to manage sql schema
6
6
  - sql-dao-generator to generate the sql-schemas + daos
7
+
8
+ ## required variables
9
+
10
+ | variable | description | example |
11
+ |----------|-------------|---------|
12
+ | `databaseTunnelHost.{prep,prod}` | ssm proxy host for database tunnel | `aws.ssmproxy.ahbodedb.prep` |
13
+ | `databaseClusterHost.{prep,prod}` | rds cluster endpoint | `ahbodedb.cluster-xxx.us-east-1.rds.amazonaws.com` |
14
+
15
+ ## ssm parameters
16
+
17
+ terraform provisions these ssm parameters for database credentials:
18
+
19
+ | parameter name | description | environments |
20
+ |----------------|-------------|--------------|
21
+ | `{namespace}.database.role.cicd.password` | password for cicd database user | prep, prod |
22
+ | `{namespace}.database.role.crud.password` | password for crud database user | prep, prod |
23
+
24
+ legacy parameters (kept for backwards compatibility):
25
+ - `{namespace}.database.admin.password`
26
+ - `{namespace}.database.service.password`
@@ -1,3 +1,6 @@
1
+ # ====================
2
+ # legacy params (kept for backwards compatibility)
3
+ # ====================
1
4
  resource "aws_ssm_parameter" "secret_database_admin_password" {
2
5
  name = "${local.parameter_store_namespace}.database.admin.password"
3
6
  type = "SecureString"
@@ -22,3 +25,39 @@ resource "aws_ssm_parameter" "secret_database_service_password" {
22
25
  ]
23
26
  }
24
27
  }
28
+
29
+ # ====================
30
+ # new role-based params (standard naming)
31
+ # ====================
32
+ # read legacy params to copy values (prod only)
33
+ data "aws_ssm_parameter" "legacy_admin_password" {
34
+ name = "${local.parameter_store_namespace}.database.admin.password"
35
+ count = var.environment == "prod" ? 1 : 0
36
+ with_decryption = true
37
+ }
38
+ data "aws_ssm_parameter" "legacy_service_password" {
39
+ name = "${local.parameter_store_namespace}.database.service.password"
40
+ count = var.environment == "prod" ? 1 : 0
41
+ with_decryption = true
42
+ }
43
+
44
+ resource "aws_ssm_parameter" "secret_database_role_cicd_password" {
45
+ name = "${local.parameter_store_namespace}.database.role.cicd.password"
46
+ type = "SecureString"
47
+ value = var.environment == "prod" ? data.aws_ssm_parameter.legacy_admin_password[0].value : "__CHANG3_ME__"
48
+ tags = local.tags
49
+ count = var.environment != "test" ? 1 : 0
50
+ lifecycle {
51
+ ignore_changes = [value]
52
+ }
53
+ }
54
+ resource "aws_ssm_parameter" "secret_database_role_crud_password" {
55
+ name = "${local.parameter_store_namespace}.database.role.crud.password"
56
+ type = "SecureString"
57
+ value = var.environment == "prod" ? data.aws_ssm_parameter.legacy_service_password[0].value : "__CHANG3_ME__"
58
+ tags = local.tags
59
+ count = var.environment != "test" ? 1 : 0
60
+ lifecycle {
61
+ ignore_changes = [value]
62
+ }
63
+ }
@@ -13,20 +13,20 @@
13
13
  ##
14
14
  ## usage example:
15
15
  ## ```sh
16
- ## ./provision/schema/deploy.database.sh dev $(op item get ahbodedb.dev.postgres --fields label=password --format json | jq -r .value)
16
+ ## ./provision/schema/deploy.database.sh prep $(op item get ahbodedb.prep.postgres --fields label=password --format json | jq -r .value)
17
17
  ## ```
18
18
  #####################################################
19
19
 
20
20
  # check that user has defined the environment that they want this key created for correctly
21
21
  ENVIRONMENT=$1;
22
- if [ "$ENVIRONMENT" != "prod" ] && [ "$ENVIRONMENT" != "dev" ]; then
23
- echo "\nerror: Environment, the first argument, must be specified as either 'prod' or 'dev'. You specified '$ENVIRONMENT'";
22
+ if [ "$ENVIRONMENT" != "prod" ] && [ "$ENVIRONMENT" != "prep" ]; then
23
+ echo "\nerror: Environment, the first argument, must be specified as either 'prod' or 'prep'. You specified '$ENVIRONMENT'";
24
24
  exit 1;
25
25
  fi
26
26
 
27
27
  # check that user is authed into correct account
28
28
  AWS_ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account');
29
- EXPECTED_AWS_ACCOUNT_ID=$([ "$ENVIRONMENT" = 'prod' ] && echo "@declapract{variable.awsAccountId.prod}" || echo "@declapract{variable.awsAccountId.dev}");
29
+ EXPECTED_AWS_ACCOUNT_ID=$([ "$ENVIRONMENT" = 'prod' ] && echo "@declapract{variable.awsAccountId.prod}" || echo "@declapract{variable.awsAccountId.prep}");
30
30
  if [ "$AWS_ACCOUNT_ID" != "$EXPECTED_AWS_ACCOUNT_ID" ]; then
31
31
  echo "\nerror: the AWS_ACCOUNT that you are signed into is not correct for the environment you specified. You are authed into account '$AWS_ACCOUNT_ID' but the correct account id for '$ENVIRONMENT' IS '$EXPECTED_AWS_ACCOUNT_ID'";
32
32
  exit 1;
@@ -41,20 +41,20 @@ fi;
41
41
 
42
42
  # check that the cicd password was provisioned, if in prod, since this is used to create the cicd user
43
43
  if [ "$ENVIRONMENT" = "prod" ]; then
44
- CICD_USER_PASSWORD=$(aws ssm get-parameter --name "@declapract{variable.organizationName}.@declapract{variable.projectName}.$ENVIRONMENT.database.admin.password" --with-decryption --output text --query Parameter.Value)
44
+ CICD_USER_PASSWORD=$(aws ssm get-parameter --name "@declapract{variable.organizationName}.@declapract{variable.projectName}.$ENVIRONMENT.database.role.cicd.password" --with-decryption --output text --query Parameter.Value)
45
45
  if [ -z "$CICD_USER_PASSWORD" ]; then
46
46
  echo "\nerror: CICD_USER_PASSWORD must be provisioned with terraform before running this"
47
47
  exit 1;
48
48
  fi;
49
- if [ "$CICD_USER_PASSWORD" = "__IGNORED__" ]; then
50
- echo "\nerror: CICD_USER_PASSWORD must be set to a value other than the default of '__IGNORED__'"
49
+ if [ "$CICD_USER_PASSWORD" = "__IGNORED__" ] || [ "$CICD_USER_PASSWORD" = "__CHANG3_ME__" ]; then
50
+ echo "\nerror: CICD_USER_PASSWORD must be set to a value other than the default placeholder"
51
51
  exit 1;
52
52
  fi;
53
53
  fi;
54
54
 
55
55
 
56
56
  # define the postgres connecition string
57
- CLUSTER_HOST=$([ "$ENVIRONMENT" = 'prod' ] && echo "@declapract{variable.databaseTunnelHost.prod}" || echo "@declapract{variable.databaseTunnelHost.dev}");
57
+ CLUSTER_HOST=$([ "$ENVIRONMENT" = 'prod' ] && echo "@declapract{variable.databaseTunnelHost.prod}" || echo "@declapract{variable.databaseTunnelHost.prep}");
58
58
  CLUSTER_CONNECTION_STRING=postgresql://postgres:$POSTGRES_ADMIN_PASSWORD@$CLUSTER_HOST:5432
59
59
  ROOT_DB_CONNECTION_STRING=$CLUSTER_CONNECTION_STRING/postgres
60
60
  SVC_DB_CONNECTION_STRING=$CLUSTER_CONNECTION_STRING/@declapract{variable.databaseName}
@@ -75,7 +75,7 @@ echo "\n 🔨 creating the schema..."
75
75
  psql $SVC_DB_CONNECTION_STRING -f $INIT_SQLS_DIR/.schema.sql
76
76
 
77
77
  if [ "$ENVIRONMENT" = "prod" ]; then
78
- echo "\n 🔨 granting reads to the datalakedb user..." # only in prod env; we dont want dev's testing data in our datalake
78
+ echo "\n 🔨 granting reads to the datalakedb user..." # only in prod env; we dont want prep's test data in our datalake
79
79
  psql $SVC_DB_CONNECTION_STRING -f $INIT_SQLS_DIR/.user.datalakedb.sql
80
80
  fi;
81
81
 
@@ -0,0 +1,7 @@
1
+ provisions github repository settings and environments via declastruct-github-sdk.
2
+
3
+ ## required variables
4
+
5
+ | variable | description | example |
6
+ |----------|-------------|---------|
7
+ | `reviewers.users` | github usernames for production deployment reviewers | `['whodisio', 'ehmpathy']` |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "devDependencies": {
3
3
  "declastruct": "@declapract{check.minVersion('1.9.1')}",
4
- "declastruct-github": "@declapract{check.minVersion('1.5.5')}"
4
+ "declastruct-github": "@declapract{check.minVersion('1.6.0')}"
5
5
  }
6
6
  }
@@ -5,6 +5,7 @@ import {
5
5
  DeclaredGithubEnvironment,
6
6
  DeclaredGithubRepo,
7
7
  DeclaredGithubRepoConfig,
8
+ DeclaredGithubRepoRuleset,
8
9
  DeclaredGithubTeamRepoAccess,
9
10
  getDeclastructGithubProvider,
10
11
  } from 'declastruct-github';
@@ -154,6 +155,37 @@ export const getResources = async (): Promise<DomainEntity<any>[]> => {
154
155
  preventSelfReview: false, // self-approval allowed if in reviewers list
155
156
  });
156
157
 
158
+ // restrict who may cut `v*` release tags to the rhelease app only
159
+ // .why = prod apply is gated on a version tag cut from main; if anyone could push a
160
+ // `v*` tag, that gate is bypassable. this ruleset blocks creation, update, and
161
+ // deletion of `v*` tags for everyone except the rhelease app — the github half
162
+ // of the prod-apply oidc guarantee, and the immutability of released tags
163
+ const rulesetReleaseTags = DeclaredGithubRepoRuleset.as({
164
+ repo,
165
+ name: 'protect-release-tags',
166
+ target: 'tag',
167
+ enforcement: 'active',
168
+
169
+ // only the rhelease github app may write `v*` tags
170
+ bypassActors: [
171
+ {
172
+ actorId: 2472031, // rhelease github app id (gh api /apps/rhelease)
173
+ actorType: 'Integration',
174
+ bypassMode: 'always',
175
+ },
176
+ ],
177
+
178
+ // applies to release tags only
179
+ conditions: {
180
+ refNameInclude: ['refs/tags/v*'],
181
+ refNameExclude: [],
182
+ },
183
+
184
+ // enforce release-tag immutability: only the bypass actor may create, move, or delete
185
+ // matched tags. a released `v1.2.3` must never be re-pointed or removed once cut
186
+ rules: [{ type: 'creation' }, { type: 'update' }, { type: 'deletion' }],
187
+ });
188
+
157
189
  // and return the full set
158
190
  return [
159
191
  repo,
@@ -162,5 +194,6 @@ export const getResources = async (): Promise<DomainEntity<any>[]> => {
162
194
  teamReleasersAccess, // must come before environments that reference this team
163
195
  envProductionOnMain,
164
196
  envProductionOnElse,
197
+ rulesetReleaseTags,
165
198
  ];
166
199
  };
@@ -5,8 +5,8 @@
5
5
  "if-env": "@declapract{check.minVersion('1.0.4')}"
6
6
  },
7
7
  "scripts": {
8
- "deploy:prune": "SLS_STAGE=$(if [ \"$ACCESS\" = 'prep' ]; then echo 'dev'; else echo \"$ACCESS\"; fi) && export COMMIT=$(npx sdk-environment get commit) && npx sls prune -n 7 --stage $SLS_STAGE",
9
- "deploy:release": "SLS_STAGE=$(if [ \"$ACCESS\" = 'prep' ]; then echo 'dev'; else echo \"$ACCESS\"; fi) && export COMMIT=$(npx sdk-environment get commit) && npm run build && sls deploy --verbose --stage $SLS_STAGE",
8
+ "deploy:prune": "export COMMIT=$(npx sdk-environment get commit) && export SLS_STAGE=$([ \"$ACCESS\" = 'prep' ] && echo 'dev' || echo \"$ACCESS\") && npx sls prune -n 7 --stage $SLS_STAGE",
9
+ "deploy:release": "export COMMIT=$(npx sdk-environment get commit) && export SLS_STAGE=$([ \"$ACCESS\" = 'prep' ] && echo 'dev' || echo \"$ACCESS\") && npm run build && sls deploy --verbose --stage $SLS_STAGE",
10
10
  "deploy:send-notification": "curl -X POST -H 'Content-type: application/json' --data \"{\\\"text\\\":\\\"$([ -z $DEPLOYER_NAME ] && git config user.name || echo $DEPLOYER_NAME) has deployed $npm_package_name@v$npm_package_version:\nhttps://github.com/@declapract{variable.organizationName}/$npm_package_name/tree/v$npm_package_version\\\"}\" @declapract{variable.slackWebhookUrl}",
11
11
  "deploy:prep": "ACCESS=prep npm run deploy:release",
12
12
  "deploy:prod": "ACCESS=prod npm run deploy:release && npm run deploy:send-notification",
@@ -10,7 +10,7 @@ plugins:
10
10
 
11
11
  custom:
12
12
  accessByStage:
13
- dev: prep
13
+ dev: prep # stage=dev deploys to $service-dev but uses ACCESS=prep (config/prep.json)
14
14
  prod: prod
15
15
 
16
16
  provider:
@@ -25,8 +25,8 @@ provider:
25
25
  product: ${self:service}
26
26
  environment:
27
27
  TZ: UTC # guarantee that utc timezone will be used explicitly, to facilitate a pit of success
28
- NODE_ENV: production # deploy with production optimizations of all resources, to make `dev` and `prod` stage deployments equivalent functionally (i.e., the same code paths in dev and prod)
29
- ACCESS: ${self:custom.accessByStage.${opt:stage}, 'prep'} # sdk-environment access tier, to target the correct config + resources (e.g., hit dev db -vs- prod db)
28
+ NODE_ENV: production # deploy with production optimizations of all resources, to make `prep` and `prod` stage deployments equivalent functionally (i.e., the same code paths in prep and prod)
29
+ ACCESS: ${self:custom.accessByStage.${opt:stage}, 'prep'} # sdk-environment access tier, to target the correct config + resources (e.g., hit prep db -vs- prod db)
30
30
  COMMIT: ${env:COMMIT} # sdk-environment commit slug, must be set by deploy command
31
31
  AWS_NODEJS_CONNECTION_REUSE_ENABLED: true # https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html
32
32
  deploymentBucket: serverless-deployment-@declapract{variable.infrastructureNamespaceId}-${self:provider.stage}
@@ -11,7 +11,7 @@ const accessInferencePolicy = ` # allow access inference from account name
11
11
 
12
12
  const accessByStageCustom = `custom:
13
13
  accessByStage:
14
- dev: prep
14
+ dev: prep # stage=dev deploys to $service-dev but uses ACCESS=prep (config/prep.json)
15
15
  prod: prod
16
16
 
17
17
  `;
@@ -70,7 +70,7 @@ export const fix: FileFixFunction = (contents) => {
70
70
  ) {
71
71
  fixed = fixed.replace(
72
72
  /STAGE: \$\{self:provider\.stage\}[^\n]*/,
73
- "ACCESS: ${self:custom.accessByStage.${opt:stage}, 'prep'} # sdk-environment access tier, to target the correct config + resources (e.g., hit dev db -vs- prod db)\n COMMIT: ${env:COMMIT} # sdk-environment commit slug, must be set by deploy command",
73
+ "ACCESS: ${self:custom.accessByStage.${opt:stage}, 'prep'} # sdk-environment access tier, to target the correct config + resources (e.g., hit prep db -vs- prod db)\n COMMIT: ${env:COMMIT} # sdk-environment commit slug, must be set by deploy command",
74
74
  );
75
75
  }
76
76
 
@@ -0,0 +1,14 @@
1
+ import { FileCheckType, type FileFixFunction } from 'declapract';
2
+
3
+ export const check = FileCheckType.EXISTS;
4
+
5
+ export const fix: FileFixFunction = (contents, context) => {
6
+ const newPath = context.relativeFilePath.replace(
7
+ /^provision\/aws\/environments\/dev\//,
8
+ 'provision/aws/environments/prep/',
9
+ );
10
+ return {
11
+ contents: contents ?? null,
12
+ relativeFilePath: newPath,
13
+ };
14
+ };
@@ -1,6 +1,6 @@
1
1
  provider "aws" {
2
2
  region = "us-east-1"
3
- allowed_account_ids = ["@declapract{variable.awsAccountId.dev}"] # `dev` environment resources are provisioned in the `dev` aws account
3
+ allowed_account_ids = ["@declapract{variable.awsAccountId.prep}"] # `prep` access tier resources are provisioned in the `prep` aws account
4
4
  }
5
5
 
6
6
  terraform {
@@ -14,5 +14,5 @@ terraform {
14
14
 
15
15
  module "product" {
16
16
  source = "../../product"
17
- environment = "dev"
17
+ environment = "dev" # infrastructure stays named 'dev', application uses ACCESS=prep
18
18
  }
@@ -1,6 +1,6 @@
1
1
  provider "aws" {
2
2
  region = "us-east-1"
3
- allowed_account_ids = ["@declapract{variable.awsAccountId.dev}"] # `test` environment resources are provisioned in the `dev` aws account
3
+ allowed_account_ids = ["@declapract{variable.awsAccountId.prep}"] # `test` environment resources are provisioned in the `prep` aws account
4
4
  }
5
5
 
6
6
  terraform {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "declapract-typescript-ehmpathy",
3
3
  "author": "ehmpathy",
4
4
  "description": "declapract best practices declarations for typescript",
5
- "version": "0.49.3",
5
+ "version": "0.49.5",
6
6
  "license": "MIT",
7
7
  "main": "src/index.js",
8
8
  "repository": "ehmpathy/declapract-typescript-ehmpathy",
@@ -76,13 +76,13 @@
76
76
  "esbuild-register": "3.6.0",
77
77
  "husky": "8.0.3",
78
78
  "jest": "30.2.0",
79
- "rhachet": "1.42.2",
79
+ "rhachet": "1.42.5",
80
80
  "rhachet-brains-anthropic": "0.4.1",
81
81
  "rhachet-brains-fireworksai": "0.1.3",
82
82
  "rhachet-brains-xai": "0.3.3",
83
- "rhachet-roles-bhrain": "0.29.10",
84
- "rhachet-roles-bhuild": "0.21.18",
85
- "rhachet-roles-ehmpathy": "1.37.0",
83
+ "rhachet-roles-bhrain": "0.29.15",
84
+ "rhachet-roles-bhuild": "0.21.19",
85
+ "rhachet-roles-ehmpathy": "1.37.6",
86
86
  "tsc-alias": "1.8.10",
87
87
  "tsx": "4.20.6",
88
88
  "type-fns": "1.21.2",