declapract-typescript-ehmpathy 0.49.2 → 0.49.4

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 (71) 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/node-service/bad-practices/simple-lambda-client/.declapract.readme.md +55 -0
  26. package/dist/practices/node-service/bad-practices/simple-lambda-client/package.json +5 -0
  27. package/dist/practices/node-service/bad-practices/simple-lambda-client/package.json.declapract.ts +27 -0
  28. package/dist/practices/node-service/bad-practices/simple-lambda-client/src/<star><star>/<star>.ts.declapract.ts +44 -0
  29. package/dist/practices/node-service/bad-practices/simple-lambda-handlers/.declapract.readme.md +52 -0
  30. package/dist/practices/node-service/bad-practices/simple-lambda-handlers/package.json +5 -0
  31. package/dist/practices/node-service/bad-practices/simple-lambda-handlers/package.json.declapract.ts +27 -0
  32. package/dist/practices/node-service/bad-practices/simple-lambda-handlers/src/<star><star>/<star>.ts.declapract.ts +50 -0
  33. package/dist/practices/node-service/best-practice/package.json +4 -1
  34. package/dist/practices/package-json-order/best-practice/package.json.declapract.ts +11 -11
  35. package/dist/practices/persist-with-rds/best-practice/.declapract.readme.md +21 -1
  36. package/dist/practices/persist-with-rds/best-practice/provision/aws/product/parameter-store.tf +39 -0
  37. package/dist/practices/persist-with-rds/best-practice/provision/schema/deploy.database.sh +9 -9
  38. package/dist/practices/provision-github/best-practice/.declapract.readme.md +7 -0
  39. package/dist/practices/rhachet/bad-practices/review-peer-artifacts/.behavior/<star><star>/.reviews/<star><star>/<star>peer-review<star>.md.declapract.ts +16 -0
  40. package/dist/practices/rhachet/bad-practices/review-peer-artifacts/.behavior/<star><star>/.reviews/peer/<star><star>/<star>.md.declapract.ts +16 -0
  41. package/dist/practices/rhachet/bad-practices/review-peer-artifacts/.declapract.readme.md +12 -0
  42. package/dist/practices/{runtime-type-checking → runtime-schemas}/bad-practices/joi/package.json.declapract.ts +4 -0
  43. package/dist/practices/runtime-schemas/bad-practices/joi/src/<star><star>/<star>.ts.declapract.ts +50 -0
  44. package/dist/practices/serverless/best-practice/package.json +2 -2
  45. package/dist/practices/serverless/best-practice/serverless.yml +3 -3
  46. package/dist/practices/serverless/best-practice/serverless.yml.declapract.ts +2 -2
  47. package/dist/practices/terraform-aws/bad-practices/old-dev-env-dir/provision/aws/environments/dev/<star>.declapract.ts +14 -0
  48. package/dist/practices/terraform-aws/best-practice/provision/aws/environments/{dev → prep}/main.tf +2 -2
  49. package/dist/practices/terraform-aws/best-practice/provision/aws/environments/test/main.tf +1 -1
  50. package/dist/useCases.yml +1 -3
  51. package/package.json +6 -5
  52. package/dist/practices/lambda-clients/bad-practices/lambda-service-client-package/.declapract.readme.md +0 -1
  53. package/dist/practices/lambda-clients/bad-practices/lambda-service-client-package/package.json +0 -5
  54. package/dist/practices/lambda-clients/bad-practices/lambda-service-client-package/package.json.declapract.ts +0 -16
  55. package/dist/practices/lambda-clients/best-practice/package.json +0 -5
  56. package/dist/practices/lambda-clients/best-practice/src/data/clients/<star>.ts +0 -1
  57. package/dist/practices/lambda-clients/best-practice/src/data/clients/<star>.ts.declapract.ts +0 -3
  58. package/dist/practices/lambda-handlers/best-practice/package.json +0 -6
  59. package/dist/practices/lambda-handlers/best-practice/package.json.declapract.ts +0 -3
  60. package/dist/practices/lambda-handlers/best-practice/src/contract/handlers/<star><star>/<star>.declapract.ts +0 -3
  61. package/dist/practices/runtime-type-checking/bad-practices/joi-types/package.json +0 -5
  62. package/dist/practices/runtime-type-checking/bad-practices/joi-types/package.json.declapract.ts +0 -18
  63. package/dist/practices/runtime-type-checking/bad-practices/old-joi-syntax-valid-input-cant-be-array/.declapract.readme.md +0 -1
  64. package/dist/practices/runtime-type-checking/bad-practices/old-joi-syntax-valid-input-cant-be-array/src/<star><star>/<star>.ts.declapract.ts +0 -15
  65. package/dist/practices/runtime-type-checking/best-practice/package.json.declapract.ts +0 -3
  66. /package/dist/practices/config/bad-practices/configs-contain-wrong-account-id/config/{dev.json.declapract.ts → prep.json.declapract.ts} +0 -0
  67. /package/dist/practices/{runtime-type-checking → runtime-schemas}/bad-practices/joi/.declapract.readme.md +0 -0
  68. /package/dist/practices/{runtime-type-checking → runtime-schemas}/bad-practices/joi/package.json +0 -0
  69. /package/dist/practices/{runtime-type-checking → runtime-schemas}/best-practice/package.json +0 -0
  70. /package/dist/practices/{lambda-clients → runtime-schemas}/best-practice/package.json.declapract.ts +0 -0
  71. /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
+ });