declapract-typescript-ehmpathy 0.39.18 → 0.40.0
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.
- package/dist/practices/cicd-common/best-practice/.github/workflows/.declastruct.yml +109 -0
- package/dist/practices/cicd-package/best-practice/.github/workflows/provision.yml +24 -0
- package/dist/practices/cicd-service/best-practice/.github/workflows/provision.yml +9 -13
- package/dist/practices/domain/best-practice/package.json +1 -1
- package/dist/practices/errors/best-practice/package.json +1 -1
- package/dist/practices/lint/best-practice/.declapract.todo.md +5 -0
- package/dist/practices/lint/best-practice/.eslintrc.js +21 -1
- package/dist/practices/lint/best-practice/package.json +1 -0
- package/dist/practices/{terraform-github/best-practice → provision-github/bad-practices/terraform-github}/provision/github/environment/main.tf +1 -1
- package/dist/practices/provision-github/best-practice/package.json +6 -0
- package/dist/practices/provision-github/best-practice/package.json.declapract.ts +3 -0
- package/dist/practices/provision-github/best-practice/provision/github/declastruct.resources.ts +124 -0
- package/dist/practices/tests/best-practice/jest.acceptance.config.ts +3 -0
- package/dist/practices/tests/best-practice/jest.acceptance.env.ts +23 -2
- package/dist/practices/tests/best-practice/jest.integration.config.ts +3 -0
- package/dist/practices/tests/best-practice/jest.integration.env.ts +23 -2
- package/dist/practices/tests/best-practice/jest.unit.config.ts +3 -0
- package/dist/practices/tests/best-practice/jest.unit.env.ts +16 -1
- package/dist/practices/tests/best-practice/jest.unit.env.ts.declapract.ts +3 -2
- package/dist/practices/tests/best-practice/package.json +1 -1
- package/dist/useCases.yml +3 -2
- package/package.json +3 -3
- package/readme.md +131 -20
- /package/dist/practices/{terraform-github/best-practice → provision-github/bad-practices/terraform-github}/provision/github/environment/import-existing-repo.sh +0 -0
- /package/dist/practices/{terraform-github/best-practice → provision-github/bad-practices/terraform-github}/provision/github/environment/versions.tf +0 -0
- /package/dist/practices/{terraform-github/best-practice → provision-github/bad-practices/terraform-github}/provision/github/product/repository.tf +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: .declastruct
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
wish-path:
|
|
7
|
+
type: string
|
|
8
|
+
description: "the path to the wish declaration"
|
|
9
|
+
github-environment:
|
|
10
|
+
type: string
|
|
11
|
+
description: "the github environment that the apply step will be executed in"
|
|
12
|
+
allow-apply:
|
|
13
|
+
type: boolean
|
|
14
|
+
description: "whether the apply step is enabled. defaults to true on main"
|
|
15
|
+
default: ${{ github.ref == 'refs/heads/main' }}
|
|
16
|
+
secrets:
|
|
17
|
+
github-token:
|
|
18
|
+
required: false
|
|
19
|
+
description: optional credentials to support authenticating with github provider
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
# install the dependencies
|
|
23
|
+
install:
|
|
24
|
+
uses: ./.github/workflows/.install.yml
|
|
25
|
+
|
|
26
|
+
plan:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
needs: [install]
|
|
29
|
+
outputs:
|
|
30
|
+
has-changes-planned: ${{ steps.evaluate-plan.outputs.has-changes-planned }}
|
|
31
|
+
steps:
|
|
32
|
+
- name: checkout
|
|
33
|
+
uses: actions/checkout@v3
|
|
34
|
+
|
|
35
|
+
- name: set node-version
|
|
36
|
+
uses: actions/setup-node@v3
|
|
37
|
+
with:
|
|
38
|
+
node-version-file: ".nvmrc"
|
|
39
|
+
|
|
40
|
+
- name: get node-modules from cache
|
|
41
|
+
uses: actions/cache/restore@v4
|
|
42
|
+
with:
|
|
43
|
+
path: ./node_modules
|
|
44
|
+
key: ${{ needs.install.outputs.node-modules-cache-key }}
|
|
45
|
+
|
|
46
|
+
- name: declastruct plan
|
|
47
|
+
id: plan
|
|
48
|
+
run: npx declastruct plan --wish ${{ inputs.wish-path }} --into ${{ inputs.wish-path }}.plan.json | tee ./plan.log
|
|
49
|
+
env:
|
|
50
|
+
GITHUB_TOKEN: ${{ secrets.github-token }} # allow specifying a github token to pass to the terraform command
|
|
51
|
+
|
|
52
|
+
- name: evaluate plan
|
|
53
|
+
id: evaluate-plan
|
|
54
|
+
run: |
|
|
55
|
+
# check whether it said there were required changes
|
|
56
|
+
if grep "Everything is in sync" ./plan.log
|
|
57
|
+
then
|
|
58
|
+
echo "has-changes-planned=false" >> "$GITHUB_OUTPUT"
|
|
59
|
+
else
|
|
60
|
+
echo "has-changes-planned=true" >> "$GITHUB_OUTPUT"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
- name: has changes planned?
|
|
64
|
+
run: echo "${{ steps.evaluate-plan.outputs.has-changes-planned }}"
|
|
65
|
+
|
|
66
|
+
- name: upload plan artifact
|
|
67
|
+
if: ${{ inputs.allow-apply == true && steps.evaluate-plan.outputs.has-changes-planned == 'true' }}
|
|
68
|
+
uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: declastruct-plan
|
|
71
|
+
path: |
|
|
72
|
+
${{ inputs.wish-path }}.plan.json
|
|
73
|
+
retention-days: 1
|
|
74
|
+
include-hidden-files: false
|
|
75
|
+
|
|
76
|
+
apply:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
environment: ${{ inputs.github-environment }}
|
|
79
|
+
needs: [install, plan]
|
|
80
|
+
if: ${{ inputs.allow-apply == true && needs.plan.outputs.has-changes-planned == 'true' }}
|
|
81
|
+
steps:
|
|
82
|
+
- name: checkout
|
|
83
|
+
uses: actions/checkout@v3
|
|
84
|
+
|
|
85
|
+
- name: set node-version
|
|
86
|
+
uses: actions/setup-node@v3
|
|
87
|
+
with:
|
|
88
|
+
node-version-file: ".nvmrc"
|
|
89
|
+
|
|
90
|
+
- name: get node-modules from cache
|
|
91
|
+
uses: actions/cache/restore@v4
|
|
92
|
+
with:
|
|
93
|
+
path: ./node_modules
|
|
94
|
+
key: ${{ needs.install.outputs.node-modules-cache-key }}
|
|
95
|
+
|
|
96
|
+
- name: extract directory from wish-path
|
|
97
|
+
id: extract-dir
|
|
98
|
+
run: echo "wish-dir=$(dirname "${{ inputs.wish-path }}")" >> "$GITHUB_OUTPUT"
|
|
99
|
+
|
|
100
|
+
- name: download plan artifact
|
|
101
|
+
uses: actions/download-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: declastruct-plan
|
|
104
|
+
path: ${{ steps.extract-dir.outputs.wish-dir }}
|
|
105
|
+
|
|
106
|
+
- name: declastruct apply
|
|
107
|
+
run: npx declastruct apply --plan ${{ inputs.wish-path }}.plan.json | tee ./apply.log
|
|
108
|
+
env:
|
|
109
|
+
GITHUB_TOKEN: ${{ secrets.github-token }} # allow specifying a github token to pass to the terraform command
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: provision
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- v*
|
|
7
|
+
branches:
|
|
8
|
+
- "main"
|
|
9
|
+
- "master"
|
|
10
|
+
pull_request:
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ${{ github.workflow }}-${{ github.ref }} # per [workflow] x [branch, tag]
|
|
15
|
+
cancel-in-progress: true # cancel workflows for non-latest commits
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
github:
|
|
19
|
+
uses: ./.github/workflows/.declastruct.yml
|
|
20
|
+
with:
|
|
21
|
+
wish-path: provision/github/declastruct.resources.ts
|
|
22
|
+
github-environment: prod
|
|
23
|
+
secrets:
|
|
24
|
+
github-token: ${{ secrets.PROVISION_GITHUB_GITHUB_TOKEN }}
|
|
@@ -5,8 +5,8 @@ on:
|
|
|
5
5
|
tags:
|
|
6
6
|
- v*
|
|
7
7
|
branches:
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- "main"
|
|
9
|
+
- "master"
|
|
10
10
|
pull_request:
|
|
11
11
|
workflow_dispatch:
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
working-directory: provision/aws/environments/test
|
|
22
22
|
github-environment: dev
|
|
23
23
|
aws-region: us-east-1
|
|
24
|
-
aws-account-id:
|
|
24
|
+
aws-account-id: "@declapract{variable.awsAccountId.dev}"
|
|
25
25
|
secrets:
|
|
26
26
|
aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
|
|
27
27
|
aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
|
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
working-directory: provision/aws/environments/dev
|
|
33
33
|
github-environment: dev
|
|
34
34
|
aws-region: us-east-1
|
|
35
|
-
aws-account-id:
|
|
35
|
+
aws-account-id: "@declapract{variable.awsAccountId.dev}"
|
|
36
36
|
secrets:
|
|
37
37
|
aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
|
|
38
38
|
aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
|
|
@@ -43,22 +43,18 @@ jobs:
|
|
|
43
43
|
working-directory: provision/aws/environments/prod
|
|
44
44
|
github-environment: prod
|
|
45
45
|
aws-region: us-east-1
|
|
46
|
-
aws-account-id:
|
|
46
|
+
aws-account-id: "@declapract{variable.awsAccountId.prod}"
|
|
47
47
|
allow-apply: ${{ startsWith(github.ref, 'refs/tags/') }} # only apply to prod on tags
|
|
48
48
|
secrets:
|
|
49
49
|
aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
|
|
50
50
|
aws-secret-access-key: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}
|
|
51
51
|
|
|
52
52
|
github:
|
|
53
|
-
uses: ./.github/workflows/.
|
|
53
|
+
uses: ./.github/workflows/.declastruct.yml
|
|
54
54
|
with:
|
|
55
|
-
|
|
55
|
+
wish-path: provision/github/declastruct.resources.ts
|
|
56
56
|
github-environment: prod
|
|
57
|
-
aws-region: us-east-1
|
|
58
|
-
aws-account-id: '@declapract{variable.awsAccountId.prod}'
|
|
59
57
|
secrets:
|
|
60
|
-
aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
|
|
61
|
-
aws-secret-access-key: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}
|
|
62
58
|
github-token: ${{ secrets.PROVISION_GITHUB_GITHUB_TOKEN }}
|
|
63
59
|
|
|
64
60
|
sql-schema-dev:
|
|
@@ -67,7 +63,7 @@ jobs:
|
|
|
67
63
|
stage: dev
|
|
68
64
|
github-environment: dev
|
|
69
65
|
aws-region: us-east-1
|
|
70
|
-
aws-account-id:
|
|
66
|
+
aws-account-id: "@declapract{variable.awsAccountId.dev}"
|
|
71
67
|
secrets:
|
|
72
68
|
aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
|
|
73
69
|
aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
|
|
@@ -79,7 +75,7 @@ jobs:
|
|
|
79
75
|
stage: prod
|
|
80
76
|
github-environment: prod
|
|
81
77
|
aws-region: us-east-1
|
|
82
|
-
aws-account-id:
|
|
78
|
+
aws-account-id: "@declapract{variable.awsAccountId.prod}"
|
|
83
79
|
allow-apply: ${{ startsWith(github.ref, 'refs/tags/') }} # only apply to prod on tags
|
|
84
80
|
secrets:
|
|
85
81
|
aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
|
|
@@ -6,6 +6,7 @@ module.exports = {
|
|
|
6
6
|
'airbnb-typescript/base', // uses the airbnb recommended rules
|
|
7
7
|
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
|
8
8
|
],
|
|
9
|
+
plugins: ['unused-imports'],
|
|
9
10
|
parserOptions: {
|
|
10
11
|
project: './tsconfig.json',
|
|
11
12
|
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
|
@@ -31,7 +32,26 @@ module.exports = {
|
|
|
31
32
|
},
|
|
32
33
|
],
|
|
33
34
|
'@typescript-eslint/no-explicit-any': 'error', // forbid any type for better type safety; you can use `never` or `unknown` instead when its truly unknown or never needed to be known
|
|
34
|
-
'@typescript-eslint/
|
|
35
|
+
'@typescript-eslint/consistent-type-imports': [
|
|
36
|
+
'error',
|
|
37
|
+
{
|
|
38
|
+
prefer: 'type-imports', // enforce using `import type` for type-only imports
|
|
39
|
+
fixStyle: 'inline-type-imports', // use inline `import { type Foo }` style
|
|
40
|
+
disallowTypeAnnotations: true, // disallow using `import type` in type annotations
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
'no-unused-vars': 'off', // turn off base rule as it can report incorrect errors
|
|
44
|
+
'@typescript-eslint/no-unused-vars': 'off', // turn off in favor of unused-imports/no-unused-vars
|
|
45
|
+
'unused-imports/no-unused-imports': 'error', // auto-fixable rule to remove unused imports
|
|
46
|
+
'unused-imports/no-unused-vars': [
|
|
47
|
+
'warn',
|
|
48
|
+
{
|
|
49
|
+
vars: 'all',
|
|
50
|
+
varsIgnorePattern: '^_',
|
|
51
|
+
args: 'after-used',
|
|
52
|
+
argsIgnorePattern: '^_',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
35
55
|
'import/no-cycle': 'off',
|
|
36
56
|
'max-classes-per-file': 'off',
|
|
37
57
|
'@typescript-eslint/no-use-before-define': 'off',
|
|
@@ -5,7 +5,7 @@ provider "github" {
|
|
|
5
5
|
|
|
6
6
|
terraform {
|
|
7
7
|
backend "s3" {
|
|
8
|
-
bucket = "terraform-state
|
|
8
|
+
bucket = "terraform-state-xdeclapract{variable.infrastructureNamespaceId}-prod" # tracked in the prod aws account's s3 bucket, so `use.@declapract{variable.organizationName}.prod`
|
|
9
9
|
key = "@declapract{variable.projectName}-github"
|
|
10
10
|
region = "us-east-1"
|
|
11
11
|
encrypt = true
|
package/dist/practices/provision-github/best-practice/provision/github/declastruct.resources.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { DeclastructProvider } from 'declastruct';
|
|
2
|
+
import {
|
|
3
|
+
DeclaredGithubBranch,
|
|
4
|
+
DeclaredGithubBranchProtection,
|
|
5
|
+
DeclaredGithubRepo,
|
|
6
|
+
DeclaredGithubRepoConfig,
|
|
7
|
+
getDeclastructGithubProvider,
|
|
8
|
+
} from 'declastruct-github';
|
|
9
|
+
import { DomainEntity, RefByUnique } from 'domain-objects';
|
|
10
|
+
import { UnexpectedCodePathError } from 'helpful-errors';
|
|
11
|
+
|
|
12
|
+
import pkg from '../../package.json';
|
|
13
|
+
|
|
14
|
+
export const getProviders = async (): Promise<DeclastructProvider[]> => [
|
|
15
|
+
getDeclastructGithubProvider(
|
|
16
|
+
{
|
|
17
|
+
credentials: {
|
|
18
|
+
token:
|
|
19
|
+
process.env.GITHUB_TOKEN ??
|
|
20
|
+
UnexpectedCodePathError.throw('github token not supplied'),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
log: {
|
|
25
|
+
info: () => {},
|
|
26
|
+
debug: () => {},
|
|
27
|
+
warn: console.warn,
|
|
28
|
+
error: console.error,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const getResources = async (): Promise<DomainEntity<any>[]> => {
|
|
35
|
+
// declare the repo
|
|
36
|
+
const repo = DeclaredGithubRepo.as({
|
|
37
|
+
owner: '@declapract{variable.organizationName}',
|
|
38
|
+
name: '@declapract{variable.projectName}',
|
|
39
|
+
description: (pkg as any).description ?? null,
|
|
40
|
+
visibility: (pkg as any).private === true ? 'private' : 'public',
|
|
41
|
+
private: (pkg as any).private ?? false, // todo: why do we have to specify this twice?
|
|
42
|
+
homepage: (pkg as any).homepage ?? null,
|
|
43
|
+
|
|
44
|
+
// things we haven't changed from the defaults
|
|
45
|
+
archived: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ref the main branch
|
|
49
|
+
const branchMain = RefByUnique.as<typeof DeclaredGithubBranch>({
|
|
50
|
+
name: 'main',
|
|
51
|
+
repo,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// declare config for the repo
|
|
55
|
+
const repoConfig = DeclaredGithubRepoConfig.as({
|
|
56
|
+
repo,
|
|
57
|
+
|
|
58
|
+
// explicitly set the main branch
|
|
59
|
+
defaultBranch: branchMain.name,
|
|
60
|
+
|
|
61
|
+
// we only use issues; the rest is noise today
|
|
62
|
+
hasIssues: true,
|
|
63
|
+
hasProjects: false,
|
|
64
|
+
hasWiki: false,
|
|
65
|
+
hasDownloads: false,
|
|
66
|
+
isTemplate: false,
|
|
67
|
+
|
|
68
|
+
// only squash merges are allowed
|
|
69
|
+
allowSquashMerge: true,
|
|
70
|
+
allowMergeCommit: false, // but especially not merge merges. never merge merges
|
|
71
|
+
allowRebaseMerge: false,
|
|
72
|
+
|
|
73
|
+
// allow nice to haves for pulls
|
|
74
|
+
allowAutoMerge: true,
|
|
75
|
+
allowUpdateBranch: true,
|
|
76
|
+
|
|
77
|
+
// always cleanup after yourself
|
|
78
|
+
deleteBranchOnMerge: true,
|
|
79
|
+
|
|
80
|
+
// configure messages
|
|
81
|
+
mergeCommitMessage: 'PR_TITLE',
|
|
82
|
+
mergeCommitTitle: 'MERGE_MESSAGE',
|
|
83
|
+
squashMergeCommitMessage: 'COMMIT_MESSAGES',
|
|
84
|
+
squashMergeCommitTitle: 'COMMIT_OR_PR_TITLE',
|
|
85
|
+
webCommitSignoffRequired: false,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// declare protection for that branch, too
|
|
89
|
+
const branchMainProtection = DeclaredGithubBranchProtection.as({
|
|
90
|
+
branch: branchMain,
|
|
91
|
+
|
|
92
|
+
enforceAdmins: true, // yes, even admins need to follow this (note: they can still take the time to go and change the settings temporarily for the exceptions)
|
|
93
|
+
allowsDeletions: false, // dont allow the `main` branch to be deleted
|
|
94
|
+
allowsForcePushes: false, // dont allow `main` branch to be force pushed to
|
|
95
|
+
requireLinearHistory: false, // # no ugly merge commits, woo! 🎉
|
|
96
|
+
|
|
97
|
+
requiredStatusChecks: {
|
|
98
|
+
strict: true, // branch must be up to date. otherwise, we dont know if it will really pass once it is merged
|
|
99
|
+
contexts: [
|
|
100
|
+
'suite / install / npm',
|
|
101
|
+
'suite / test-commits',
|
|
102
|
+
'suite / test-types',
|
|
103
|
+
'suite / test-format',
|
|
104
|
+
'suite / test-lint',
|
|
105
|
+
'suite / test-unit',
|
|
106
|
+
'suite / test-integration',
|
|
107
|
+
'suite / test-acceptance-locally',
|
|
108
|
+
'pullreq-title', // "review / pullreq-title",
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// things we haven't changed from the defaults
|
|
113
|
+
allowForkSyncing: false,
|
|
114
|
+
blockCreations: false,
|
|
115
|
+
lockBranch: false,
|
|
116
|
+
requiredConversationResolution: false,
|
|
117
|
+
requiredPullRequestReviews: null,
|
|
118
|
+
requiredSignatures: false,
|
|
119
|
+
restrictions: null,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// and return the full set
|
|
123
|
+
return [repo, repoConfig, branchMainProtection];
|
|
124
|
+
};
|
|
@@ -22,6 +22,9 @@ const config: Config = {
|
|
|
22
22
|
testMatch: ['**/*.acceptance.test.ts'],
|
|
23
23
|
setupFiles: ['core-js'],
|
|
24
24
|
setupFilesAfterEnv: ['./jest.acceptance.env.ts'],
|
|
25
|
+
|
|
26
|
+
// use 50% of threads to leave headroom for other processes
|
|
27
|
+
maxWorkers: '50%', // https://stackoverflow.com/questions/71287710/why-does-jest-run-faster-with-maxworkers-50
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
// eslint-disable-next-line import/no-default-export
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import util from 'util';
|
|
4
|
+
|
|
1
5
|
// eslint-disable-next-line no-undef
|
|
2
6
|
jest.setTimeout(90000); // we're calling downstream apis
|
|
3
7
|
|
|
8
|
+
// set console.log to not truncate nested objects
|
|
9
|
+
util.inspect.defaultOptions.depth = 5;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* .what = verify that we're running from a valid project directory; otherwise, fail fast
|
|
13
|
+
* .why = prevent confusion and hard-to-debug errors from running tests in the wrong directory
|
|
14
|
+
*/
|
|
15
|
+
if (!existsSync(join(process.cwd(), 'package.json')))
|
|
16
|
+
throw new Error('no package.json found in cwd. are you @gitroot?');
|
|
17
|
+
|
|
4
18
|
/**
|
|
5
|
-
* .what = verify that the env has sufficient auth to run the tests; otherwise, fail fast
|
|
19
|
+
* .what = verify that the env has sufficient auth to run the tests if aws is used; otherwise, fail fast
|
|
6
20
|
* .why =
|
|
7
21
|
* - prevent time wasted waiting on tests to fail due to lack of credentials
|
|
8
22
|
* - prevent time wasted debugging tests which are failing due to hard-to-read missed credential errors
|
|
9
23
|
*/
|
|
10
|
-
|
|
24
|
+
const declapractUsePath = join(process.cwd(), 'declapract.use.ts');
|
|
25
|
+
const requiresAwsAuth =
|
|
26
|
+
existsSync(declapractUsePath) &&
|
|
27
|
+
readFileSync(declapractUsePath, 'utf8').includes('awsAccountId');
|
|
28
|
+
if (
|
|
29
|
+
requiresAwsAuth &&
|
|
30
|
+
!(process.env.AWS_PROFILE || process.env.AWS_ACCESS_KEY_ID)
|
|
31
|
+
)
|
|
11
32
|
throw new Error(
|
|
12
33
|
'no aws credentials present. please authenticate with aws to run acceptance tests',
|
|
13
34
|
);
|
|
@@ -22,6 +22,9 @@ const config: Config = {
|
|
|
22
22
|
testMatch: ['**/*.integration.test.ts'],
|
|
23
23
|
setupFiles: ['core-js'],
|
|
24
24
|
setupFilesAfterEnv: ['./jest.integration.env.ts'],
|
|
25
|
+
|
|
26
|
+
// use 50% of threads to leave headroom for other processes
|
|
27
|
+
maxWorkers: '50%', // https://stackoverflow.com/questions/71287710/why-does-jest-run-faster-with-maxworkers-50
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
// eslint-disable-next-line import/no-default-export
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import util from 'util';
|
|
4
|
+
|
|
1
5
|
// eslint-disable-next-line no-undef
|
|
2
6
|
jest.setTimeout(90000); // since we're calling downstream apis
|
|
3
7
|
|
|
8
|
+
// set console.log to not truncate nested objects
|
|
9
|
+
util.inspect.defaultOptions.depth = 5;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* .what = verify that we're running from a valid project directory; otherwise, fail fast
|
|
13
|
+
* .why = prevent confusion and hard-to-debug errors from running tests in the wrong directory
|
|
14
|
+
*/
|
|
15
|
+
if (!existsSync(join(process.cwd(), 'package.json')))
|
|
16
|
+
throw new Error('no package.json found in cwd. are you @gitroot?');
|
|
17
|
+
|
|
4
18
|
/**
|
|
5
19
|
* sanity check that unit tests are only run the 'test' environment
|
|
6
20
|
*
|
|
@@ -15,12 +29,19 @@ if (
|
|
|
15
29
|
throw new Error(`integration.test is not targeting stage 'test'`);
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
|
-
* .what = verify that the env has sufficient auth to run the tests; otherwise, fail fast
|
|
32
|
+
* .what = verify that the env has sufficient auth to run the tests if aws is used; otherwise, fail fast
|
|
19
33
|
* .why =
|
|
20
34
|
* - prevent time wasted waiting on tests to fail due to lack of credentials
|
|
21
35
|
* - prevent time wasted debugging tests which are failing due to hard-to-read missed credential errors
|
|
22
36
|
*/
|
|
23
|
-
|
|
37
|
+
const declapractUsePath = join(process.cwd(), 'declapract.use.ts');
|
|
38
|
+
const requiresAwsAuth =
|
|
39
|
+
existsSync(declapractUsePath) &&
|
|
40
|
+
readFileSync(declapractUsePath, 'utf8').includes('awsAccountId');
|
|
41
|
+
if (
|
|
42
|
+
requiresAwsAuth &&
|
|
43
|
+
!(process.env.AWS_PROFILE || process.env.AWS_ACCESS_KEY_ID)
|
|
44
|
+
)
|
|
24
45
|
throw new Error(
|
|
25
46
|
'no aws credentials present. please authenticate with aws to run integration tests',
|
|
26
47
|
);
|
|
@@ -27,6 +27,9 @@ const config: Config = {
|
|
|
27
27
|
],
|
|
28
28
|
setupFiles: ['core-js'],
|
|
29
29
|
setupFilesAfterEnv: ['./jest.unit.env.ts'],
|
|
30
|
+
|
|
31
|
+
// use 50% of threads to leave headroom for other processes
|
|
32
|
+
maxWorkers: '50%', // https://stackoverflow.com/questions/71287710/why-does-jest-run-faster-with-maxworkers-50
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
// eslint-disable-next-line import/no-default-export
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import util from 'util';
|
|
4
|
+
|
|
5
|
+
// mock that getConfig just returns plaintext test env config in unit tests
|
|
1
6
|
jest.mock('./src/utils/config/getConfig', () => ({
|
|
2
|
-
getConfig: jest.fn().mockImplementation(() => require('./config/test.json')),
|
|
7
|
+
getConfig: jest.fn().mockImplementation(() => require('./config/test.json')),
|
|
3
8
|
}));
|
|
4
9
|
|
|
10
|
+
// set console.log to not truncate nested objects
|
|
11
|
+
util.inspect.defaultOptions.depth = 5;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* .what = verify that we're running from a valid project directory; otherwise, fail fast
|
|
15
|
+
* .why = prevent confusion and hard-to-debug errors from running tests in the wrong directory
|
|
16
|
+
*/
|
|
17
|
+
if (!existsSync(join(process.cwd(), 'package.json')))
|
|
18
|
+
throw new Error('no package.json found in cwd. are you @gitroot?');
|
|
19
|
+
|
|
5
20
|
/**
|
|
6
21
|
* sanity check that unit tests are only run the 'test' environment
|
|
7
22
|
*
|
|
@@ -15,8 +15,9 @@ export const contents: FileContentsFunction = async (context) => {
|
|
|
15
15
|
let contents = contentsSuperset;
|
|
16
16
|
if (!context.projectPractices.includes('config'))
|
|
17
17
|
contents = contents.replace(
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
`// mock that getConfig just returns plaintext test env config in unit tests
|
|
19
|
+
jest.mock('./src/utils/config/getConfig', () => ({
|
|
20
|
+
getConfig: jest.fn().mockImplementation(() => require('./config/test.json')),
|
|
20
21
|
}));
|
|
21
22
|
|
|
22
23
|
`,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"@types/jest": "@declapract{check.minVersion('29.2.4')}",
|
|
4
4
|
"jest": "@declapract{check.minVersion('29.3.1')}",
|
|
5
5
|
"test-fns": "@declapract{check.minVersion('1.4.2')}",
|
|
6
|
-
"ts-jest": "@declapract{check.minVersion('29.
|
|
6
|
+
"ts-jest": "@declapract{check.minVersion('29.4.5')}",
|
|
7
7
|
"ts-node": "@declapract{check.minVersion('10.9.2')}",
|
|
8
8
|
"core-js": "@declapract{check.minVersion('3.26.1')}",
|
|
9
9
|
"@babel/core": "@declapract{check.minVersion('7.28.5')}",
|
package/dist/useCases.yml
CHANGED
|
@@ -23,6 +23,7 @@ use-cases:
|
|
|
23
23
|
practices:
|
|
24
24
|
- cicd-package
|
|
25
25
|
- node-package
|
|
26
|
+
- provision-github
|
|
26
27
|
lambda-service:
|
|
27
28
|
extends:
|
|
28
29
|
- typescript-project
|
|
@@ -40,8 +41,8 @@ use-cases:
|
|
|
40
41
|
- node-service
|
|
41
42
|
- runtime-type-checking
|
|
42
43
|
- serverless
|
|
44
|
+
- provision-github
|
|
43
45
|
- terraform-common
|
|
44
|
-
- terraform-github
|
|
45
46
|
- terraform-aws
|
|
46
47
|
- tests-service
|
|
47
48
|
- uuid
|
|
@@ -70,7 +71,7 @@ use-cases:
|
|
|
70
71
|
- package-json-order
|
|
71
72
|
- prettier
|
|
72
73
|
- terraform-common
|
|
73
|
-
-
|
|
74
|
+
- provision-github
|
|
74
75
|
- lint
|
|
75
76
|
- lint-react
|
|
76
77
|
- lint-react-native
|
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.
|
|
5
|
+
"version": "0.40.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"prepare:husky": "npx husky install && chmod ug+x .husky/*"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"domain-objects": "0.
|
|
44
|
+
"domain-objects": "0.29.2",
|
|
45
45
|
"expect": "29.4.2",
|
|
46
46
|
"flat": "5.0.2",
|
|
47
47
|
"helpful-errors": "1.3.8",
|
|
@@ -68,12 +68,12 @@
|
|
|
68
68
|
"eslint-config-prettier": "8.5.0",
|
|
69
69
|
"eslint-plugin-import": "2.26.0",
|
|
70
70
|
"eslint-plugin-prettier": "4.2.1",
|
|
71
|
+
"eslint-plugin-unused-imports": "4.3.0",
|
|
71
72
|
"husky": "8.0.3",
|
|
72
73
|
"jest": "29.3.1",
|
|
73
74
|
"prettier": "2.8.1",
|
|
74
75
|
"test-fns": "1.4.2",
|
|
75
76
|
"ts-jest": "29.1.3",
|
|
76
|
-
"ts-node": "10.9.2",
|
|
77
77
|
"type-fns": "0.8.1",
|
|
78
78
|
"typescript": "5.4.5",
|
|
79
79
|
"visualogic": "1.2.3"
|
package/readme.md
CHANGED
|
@@ -1,39 +1,150 @@
|
|
|
1
1
|
# declapract-typescript-ehmpathy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Declared best practices for TypeScript projects.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Sync your projects with the latest and greatest best practices with codemods powered by [declapract](https://github.com/ehmpathy/declapract).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## What is this?
|
|
8
|
+
|
|
9
|
+
This package contains battle-tested TypeScript best practices from the [ehmpathy org](https://github.com/ehmpathy), packaged for automated enforcement via codemods through [declapract](https://github.com/ehmpathy/declapract).
|
|
10
|
+
|
|
11
|
+
Instead of adhoc configuration of linters, formatters, test frameworks, cicd pipelines, and infrastructure for every project, this package provides:
|
|
12
|
+
|
|
13
|
+
- **40+ declared practices** covering TypeScript development, testing, CI/CD, AWS infrastructure, and more
|
|
14
|
+
- **Automated checking** to verify your repo follows best practices - and avoids bad practices
|
|
15
|
+
- **Automatic fixes** for most common issues
|
|
16
|
+
- **Project templates** to bootstrap new projects with everything configured
|
|
17
|
+
|
|
18
|
+
## How to use it?
|
|
19
|
+
|
|
20
|
+
### 1. Install declapract
|
|
8
21
|
|
|
9
|
-
1. add `declapract` to your repo
|
|
10
22
|
```sh
|
|
11
23
|
npm install --save-dev declapract
|
|
12
24
|
```
|
|
13
25
|
|
|
14
|
-
2.
|
|
26
|
+
### 2. Configure your project
|
|
27
|
+
|
|
28
|
+
Create `declapract.use.yml` in your repo:
|
|
29
|
+
|
|
15
30
|
```yml
|
|
16
|
-
# declapract.use.yml
|
|
17
31
|
declarations: npm:declapract-typescript-ehmpathy
|
|
18
|
-
useCase:
|
|
19
|
-
variables:
|
|
20
|
-
organizationName: '
|
|
21
|
-
projectName: '
|
|
22
|
-
infrastructureNamespaceId: '
|
|
23
|
-
slackReleaseWebHook: 'https
|
|
32
|
+
useCase: typescript-project # Choose from: typescript-project, npm-package, lambda-service, lambda-service-with-rds, lambda-service-with-dynamodb, app-react-native-expo
|
|
33
|
+
variables:
|
|
34
|
+
organizationName: 'my-org'
|
|
35
|
+
projectName: 'my-project'
|
|
36
|
+
infrastructureNamespaceId: 'prod-abc123'
|
|
37
|
+
slackReleaseWebHook: 'https://hooks.slack.com/...'
|
|
24
38
|
```
|
|
25
39
|
|
|
26
|
-
3.
|
|
40
|
+
### 3. Plan changes
|
|
41
|
+
|
|
27
42
|
```sh
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
# plan all practices
|
|
44
|
+
declapract plan
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
declapract check
|
|
46
|
+
# or scope to one
|
|
47
|
+
declapract plan --practice
|
|
34
48
|
```
|
|
35
49
|
|
|
36
|
-
|
|
50
|
+
### 4. Apply changes
|
|
51
|
+
|
|
37
52
|
```sh
|
|
38
|
-
|
|
53
|
+
# apply all practices
|
|
54
|
+
declapract apply
|
|
55
|
+
|
|
56
|
+
# or scope to one
|
|
57
|
+
declapract apply --practice
|
|
39
58
|
```
|
|
59
|
+
|
|
60
|
+
## Benefits
|
|
61
|
+
|
|
62
|
+
### Consistency
|
|
63
|
+
All projects follow the same patterns, making it easy to switch between codebases
|
|
64
|
+
|
|
65
|
+
### Quality
|
|
66
|
+
Enforced best practices catch issues before code review
|
|
67
|
+
|
|
68
|
+
### Speed
|
|
69
|
+
Bootstrap new projects in minutes instead of hours
|
|
70
|
+
|
|
71
|
+
### Maintenance
|
|
72
|
+
Update best practices across all repos by updating one dependency
|
|
73
|
+
|
|
74
|
+
### Documentation
|
|
75
|
+
Practices are explicitly declared and versioned, serving as living documentation
|
|
76
|
+
|
|
77
|
+
## What use cases are supported?
|
|
78
|
+
|
|
79
|
+
This package defines complete sets of practices for common project types:
|
|
80
|
+
|
|
81
|
+
### `typescript-project`
|
|
82
|
+
Base configuration for any TypeScript project (18 core practices)
|
|
83
|
+
|
|
84
|
+
### `npm-package`
|
|
85
|
+
TypeScript packages published to npm (adds CI/CD for publishing)
|
|
86
|
+
|
|
87
|
+
### `lambda-service`
|
|
88
|
+
AWS Lambda microservices (adds AWS infrastructure, handlers, config, logging)
|
|
89
|
+
|
|
90
|
+
### `lambda-service-with-rds`
|
|
91
|
+
Lambda services with PostgreSQL database (adds RDS provisioning and DAOs)
|
|
92
|
+
|
|
93
|
+
### `lambda-service-with-dynamodb`
|
|
94
|
+
Lambda services with DynamoDB (adds DynamoDB DAOs)
|
|
95
|
+
|
|
96
|
+
### `app-react-native-expo`
|
|
97
|
+
React Native mobile apps with Expo
|
|
98
|
+
|
|
99
|
+
## What practices are included?
|
|
100
|
+
|
|
101
|
+
### Core TypeScript Development
|
|
102
|
+
- **typescript** - Strict TypeScript configuration
|
|
103
|
+
- **lint** - ESLint with TypeScript rules (airbnb-typescript)
|
|
104
|
+
- **format** - Prettier configuration
|
|
105
|
+
- **domain** - Domain-driven design with `domain-objects`
|
|
106
|
+
- **directory-structure-src** - Consistent project structures
|
|
107
|
+
- **runtime-type-checking** - Type-safe validation with `domain-objects`
|
|
108
|
+
- **errors** - Structured error handling with `helpful-errors`
|
|
109
|
+
- etc
|
|
110
|
+
|
|
111
|
+
### Tests & Quality
|
|
112
|
+
- **tests** - Jest configuration for unit, integration, and acceptance tests
|
|
113
|
+
- **conventional-commits** - Enforced commit message standards
|
|
114
|
+
- **husky** - Git hooks for pre-commit validation
|
|
115
|
+
- etc
|
|
116
|
+
|
|
117
|
+
### CI/CD
|
|
118
|
+
- **cicd-common** - GitHub Actions workflows for testing
|
|
119
|
+
- **cicd-package** - Publishing workflows for npm packages
|
|
120
|
+
- **cicd-service** - Deployment workflows for services
|
|
121
|
+
|
|
122
|
+
### AWS Lambda Services
|
|
123
|
+
- **lambda-handlers** - Standard Lambda function structure
|
|
124
|
+
- **lambda-clients** - Type-safe Lambda client wrappers
|
|
125
|
+
- **config** - Environment configuration with AWS Parameter Store
|
|
126
|
+
- **logs** - Structured logging with `simple-log-methods`
|
|
127
|
+
- **uuid** - UUID generation standards
|
|
128
|
+
- etc
|
|
129
|
+
|
|
130
|
+
### Infrastructure
|
|
131
|
+
- **terraform-common** - Common Terraform patterns
|
|
132
|
+
- **terraform-aws** - AWS-specific Terraform modules
|
|
133
|
+
- **declastruct-github** - GitHub repository configuration
|
|
134
|
+
- **environments-aws** - Multi-environment setup (dev/test/prod)
|
|
135
|
+
- etc
|
|
136
|
+
|
|
137
|
+
### Persistence
|
|
138
|
+
- **persist-with-rds** - PostgreSQL with `sql-dao-generator`
|
|
139
|
+
- **persist-with-dynamodb** - DynamoDB DAO patterns
|
|
140
|
+
|
|
141
|
+
### React Native
|
|
142
|
+
- **app-react-native-expo** - Expo app configuration
|
|
143
|
+
- **lint-react** - React-specific linting
|
|
144
|
+
- **lint-react-native** - React Native linting
|
|
145
|
+
|
|
146
|
+
## Learn more
|
|
147
|
+
|
|
148
|
+
- [declapract documentation](https://github.com/ehmpathy/declapract)
|
|
149
|
+
- [full list of practices](./src/practices)
|
|
150
|
+
- [full list of usecases](./src/useCases.yml)
|
|
File without changes
|
|
File without changes
|