@cluerise/tools 3.0.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/README.md +52 -0
- package/dist/bin/cluenar-tools.sh +10 -0
- package/dist/configs/.editorconfig +10 -0
- package/dist/configs/.eslintignore +47 -0
- package/dist/configs/.eslintrc.cjs +114 -0
- package/dist/configs/.nvmrc +1 -0
- package/dist/configs/.prettierignore +37 -0
- package/dist/configs/_gitignore +23 -0
- package/dist/configs/_npmrc +1 -0
- package/dist/configs/commitlint.config.ts +31 -0
- package/dist/configs/hooks/commit-msg +1 -0
- package/dist/configs/hooks/post-commit +1 -0
- package/dist/configs/hooks/pre-commit +1 -0
- package/dist/configs/hooks/prepare-commit-msg +1 -0
- package/dist/configs/lint-staged.config.js +31 -0
- package/dist/configs/prettier.config.js +13 -0
- package/dist/configs/release.config.js +119 -0
- package/dist/configs/tsconfig.json +27 -0
- package/dist/scripts/check-heroku-node-version/main.js +138 -0
- package/dist/scripts/create-commit-message/main.js +149 -0
- package/dist/scripts/format-commit-message/main.js +130 -0
- package/dist/scripts/init/main.js +386 -0
- package/dist/scripts/lint/main.js +161 -0
- package/dist/scripts/release/main.js +230 -0
- package/dist/scripts/update-node-versions/main.js +162 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Cluerise Tools
|
|
2
|
+
|
|
3
|
+
_Tools for maintaining TypeScript projects_
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### Init
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx @cluerise/tools init [all|name]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Before you run the `init` command, make sure your `package.json` contains
|
|
14
|
+
|
|
15
|
+
- the `"type"` field set to `"module"`,
|
|
16
|
+
- the `"repository"` field set to a valid value (e.g. `"github:org/repo"`).
|
|
17
|
+
|
|
18
|
+
### Lint
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
cluerise-tools lint [all|code|staged|commit]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Release
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
cluerise-tools release [create|extract-changelog]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Update Node.js versions
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
cluerise-tools update-node-versions
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Create Commit Message
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
cluerise-tools create-commit-message
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Format Commit Message
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
cluerise-tools format-commit-message
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Check Heroku Node.js Version
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
cluerise-tools check-heroku-node-version
|
|
52
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# All hidden files and directories
|
|
2
|
+
.*
|
|
3
|
+
|
|
4
|
+
# Enable hidden files with extensions
|
|
5
|
+
!.*.*
|
|
6
|
+
|
|
7
|
+
# Enable all hidden directories
|
|
8
|
+
!.*/
|
|
9
|
+
|
|
10
|
+
# Archives
|
|
11
|
+
*.zip
|
|
12
|
+
*.gz
|
|
13
|
+
*.tgz
|
|
14
|
+
|
|
15
|
+
# Build
|
|
16
|
+
build/
|
|
17
|
+
dist/
|
|
18
|
+
|
|
19
|
+
# Dependencies
|
|
20
|
+
node_modules/
|
|
21
|
+
|
|
22
|
+
# Git
|
|
23
|
+
.git/
|
|
24
|
+
|
|
25
|
+
# Git hooks
|
|
26
|
+
hooks/*
|
|
27
|
+
!hooks/*.js
|
|
28
|
+
!hooks/*.ts
|
|
29
|
+
|
|
30
|
+
# Lock files
|
|
31
|
+
package-lock.json
|
|
32
|
+
yarn.lock
|
|
33
|
+
|
|
34
|
+
# Misc
|
|
35
|
+
etc/
|
|
36
|
+
|
|
37
|
+
# Sample of .env and .envrc files
|
|
38
|
+
.env.example
|
|
39
|
+
.env.sample
|
|
40
|
+
.envrc.example
|
|
41
|
+
.envrc.sample
|
|
42
|
+
|
|
43
|
+
# Shell scripts
|
|
44
|
+
*.sh
|
|
45
|
+
|
|
46
|
+
# Tests coverage
|
|
47
|
+
coverage/
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const importRestrictions = {
|
|
2
|
+
srcDirectory: {
|
|
3
|
+
group: ['src/*'],
|
|
4
|
+
message: '\n Error: Do not import from the src directory'
|
|
5
|
+
},
|
|
6
|
+
nestedDirectories: {
|
|
7
|
+
group: ['./*/*'],
|
|
8
|
+
message: '\n Error: Do not import from nested directories'
|
|
9
|
+
},
|
|
10
|
+
nestedModules: {
|
|
11
|
+
group: Array.from({ length: 10 }, (_, i) => `:/modules${'/*'.repeat(i + 2)}`),
|
|
12
|
+
message: '\n Error: Do not import from nested modules'
|
|
13
|
+
},
|
|
14
|
+
nestedDirectoriesInParentModules: {
|
|
15
|
+
group: Array.from({ length: 10 }, (_, i) => [`${`../`.repeat(i + 1)}*/**`, `!${`../`.repeat(i + 1)}../*`]).flat(),
|
|
16
|
+
message: '\n Error: Do not import from nested directories in parent modules'
|
|
17
|
+
},
|
|
18
|
+
appDirectoryInModules: {
|
|
19
|
+
group: [':/app', ':/app/**'],
|
|
20
|
+
message: '\n Error: Do not import from the app directory in the modules'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const commonImportRestrictions = [
|
|
25
|
+
importRestrictions.srcDirectory,
|
|
26
|
+
importRestrictions.nestedDirectories,
|
|
27
|
+
importRestrictions.nestedModules,
|
|
28
|
+
importRestrictions.nestedDirectoriesInParentModules
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
env: {
|
|
33
|
+
es6: true,
|
|
34
|
+
node: true
|
|
35
|
+
},
|
|
36
|
+
extends: [
|
|
37
|
+
'eslint:recommended',
|
|
38
|
+
'plugin:@typescript-eslint/recommended',
|
|
39
|
+
'plugin:prettier/recommended',
|
|
40
|
+
'plugin:json/recommended-with-comments',
|
|
41
|
+
'plugin:markdown/recommended-legacy',
|
|
42
|
+
'plugin:yml/standard'
|
|
43
|
+
],
|
|
44
|
+
parser: '@typescript-eslint/parser',
|
|
45
|
+
parserOptions: {
|
|
46
|
+
ecmaVersion: 2018,
|
|
47
|
+
sourceType: 'module'
|
|
48
|
+
},
|
|
49
|
+
plugins: ['import', 'simple-import-sort', '@html-eslint'],
|
|
50
|
+
rules: {
|
|
51
|
+
'@typescript-eslint/ban-ts-comment': 'off',
|
|
52
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
53
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
54
|
+
|
|
55
|
+
'import/first': 'error',
|
|
56
|
+
'import/newline-after-import': 'error',
|
|
57
|
+
'import/no-amd': 'error',
|
|
58
|
+
'import/no-commonjs': 'error',
|
|
59
|
+
'import/no-default-export': 'error',
|
|
60
|
+
'import/no-duplicates': 'error',
|
|
61
|
+
'import/no-mutable-exports': 'error',
|
|
62
|
+
'import/no-unassigned-import': 'error',
|
|
63
|
+
'import/order': 'off',
|
|
64
|
+
|
|
65
|
+
'no-restricted-imports': ['error', { patterns: commonImportRestrictions }],
|
|
66
|
+
'no-undef-init': 'error',
|
|
67
|
+
'object-shorthand': 'error',
|
|
68
|
+
|
|
69
|
+
'simple-import-sort/exports': 'error',
|
|
70
|
+
'simple-import-sort/imports': 'error',
|
|
71
|
+
|
|
72
|
+
'sort-imports': 'off',
|
|
73
|
+
|
|
74
|
+
'yml/no-empty-mapping-value': 'off',
|
|
75
|
+
'yml/quotes': ['error', { prefer: 'single' }]
|
|
76
|
+
},
|
|
77
|
+
overrides: [
|
|
78
|
+
{
|
|
79
|
+
files: ['*.cjs', '*.cts'],
|
|
80
|
+
rules: {
|
|
81
|
+
'import/no-commonjs': 'off',
|
|
82
|
+
'@typescript-eslint/no-var-requires': 'off'
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
files: ['*.config.js', '*.config.ts'],
|
|
87
|
+
rules: {
|
|
88
|
+
'import/no-default-export': 'off'
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
files: ['src/modules/**'],
|
|
93
|
+
rules: {
|
|
94
|
+
'no-restricted-imports': [
|
|
95
|
+
'error',
|
|
96
|
+
{ patterns: [importRestrictions.appDirectoryInModules, ...commonImportRestrictions] }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
files: ['*.yaml', '*.yml'],
|
|
102
|
+
parser: 'yaml-eslint-parser'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
files: ['*.html'],
|
|
106
|
+
parser: '@html-eslint/parser',
|
|
107
|
+
extends: ['plugin:@html-eslint/recommended'],
|
|
108
|
+
rules: {
|
|
109
|
+
'@html-eslint/require-title': 'off',
|
|
110
|
+
'@html-eslint/indent': ['error', 2]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v22.1.0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# All hidden files and directories
|
|
2
|
+
.*
|
|
3
|
+
|
|
4
|
+
# Enable hidden files with extensions
|
|
5
|
+
!.*.*
|
|
6
|
+
|
|
7
|
+
# Enable all hidden directories
|
|
8
|
+
!.*/
|
|
9
|
+
|
|
10
|
+
# Build
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
|
|
14
|
+
# Dependencies
|
|
15
|
+
node_modules/
|
|
16
|
+
|
|
17
|
+
# Git
|
|
18
|
+
.git/
|
|
19
|
+
|
|
20
|
+
# Git hooks
|
|
21
|
+
hooks/*
|
|
22
|
+
!hooks/*.js
|
|
23
|
+
!hooks/*.ts
|
|
24
|
+
|
|
25
|
+
# Lock files
|
|
26
|
+
package-lock.json
|
|
27
|
+
yarn.lock
|
|
28
|
+
|
|
29
|
+
# Misc
|
|
30
|
+
etc/
|
|
31
|
+
|
|
32
|
+
# Sample of .env file
|
|
33
|
+
.env.example
|
|
34
|
+
.env.sample
|
|
35
|
+
|
|
36
|
+
# Tests coverage
|
|
37
|
+
coverage/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Build
|
|
2
|
+
build/
|
|
3
|
+
dist/
|
|
4
|
+
|
|
5
|
+
# Dependencies
|
|
6
|
+
node_modules/
|
|
7
|
+
|
|
8
|
+
# .env, .envrc
|
|
9
|
+
.env
|
|
10
|
+
.envrc
|
|
11
|
+
|
|
12
|
+
# Lint Staged
|
|
13
|
+
.lint-staged-temp-tsconfig.json
|
|
14
|
+
|
|
15
|
+
# Misc
|
|
16
|
+
.DS_Store
|
|
17
|
+
etc/
|
|
18
|
+
|
|
19
|
+
# Stryker
|
|
20
|
+
.stryker-tmp/
|
|
21
|
+
|
|
22
|
+
# Tests coverage
|
|
23
|
+
coverage/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
engine-strict = true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RuleConfigSeverity, UserConfig } from '@commitlint/types';
|
|
2
|
+
|
|
3
|
+
const commitlintConfig: UserConfig = {
|
|
4
|
+
parserPreset: 'conventional-changelog-conventionalcommits',
|
|
5
|
+
rules: {
|
|
6
|
+
'body-full-stop': [RuleConfigSeverity.Error, 'always', '.'],
|
|
7
|
+
'body-leading-blank': [RuleConfigSeverity.Error, 'always'],
|
|
8
|
+
'body-case': [RuleConfigSeverity.Error, 'always', 'sentence-case'],
|
|
9
|
+
|
|
10
|
+
'footer-leading-blank': [RuleConfigSeverity.Error, 'always'],
|
|
11
|
+
|
|
12
|
+
'header-full-stop': [RuleConfigSeverity.Error, 'never', '.'],
|
|
13
|
+
'header-max-length': [RuleConfigSeverity.Error, 'always', 100],
|
|
14
|
+
|
|
15
|
+
'scope-case': [RuleConfigSeverity.Error, 'always', 'lower-case'],
|
|
16
|
+
'scope-enum': [RuleConfigSeverity.Error, 'always', ['actions', 'dev', 'lock', 'prod', 'security']],
|
|
17
|
+
|
|
18
|
+
'subject-case': [RuleConfigSeverity.Error, 'always', 'sentence-case'],
|
|
19
|
+
'subject-empty': [RuleConfigSeverity.Error, 'never'],
|
|
20
|
+
|
|
21
|
+
'type-case': [RuleConfigSeverity.Error, 'always', 'lower-case'],
|
|
22
|
+
'type-enum': [
|
|
23
|
+
RuleConfigSeverity.Error,
|
|
24
|
+
'always',
|
|
25
|
+
['chore', 'deps', 'docs', 'feat', 'fix', 'perf', 'refactor', 'release', 'revert', 'style', 'test']
|
|
26
|
+
],
|
|
27
|
+
'type-empty': [RuleConfigSeverity.Error, 'never']
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default commitlintConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run __git-hook:commit-msg --if-present "$@"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run __git-hook:post-commit --if-present "$@"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run __git-hook:pre-commit --if-present "$@"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run __git-hook:prepare-commit-msg --if-present "$@"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import FileSystem from 'fs';
|
|
2
|
+
import Path from 'path';
|
|
3
|
+
|
|
4
|
+
const tsconfigPath = '.lint-staged-temp-tsconfig.json';
|
|
5
|
+
|
|
6
|
+
const toRelative = (filename) => Path.relative('.', filename);
|
|
7
|
+
const mapToRelative = (filenames) => filenames.map(toRelative);
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
'**': (filenames) => {
|
|
11
|
+
const relativeFilenames = mapToRelative(filenames).join(' ');
|
|
12
|
+
|
|
13
|
+
return [`eslint ${relativeFilenames}`, `prettier --check ${relativeFilenames}`];
|
|
14
|
+
},
|
|
15
|
+
'**/*.ts?(x)': (filenames) => {
|
|
16
|
+
// The lint-staged command runs this function multiple times to prepare
|
|
17
|
+
// console messages. We want to save the temporary tsconfig.json file only
|
|
18
|
+
// when this function is run with filenames (that start with a slash)
|
|
19
|
+
if (filenames.length > 0 && filenames[0].startsWith('/')) {
|
|
20
|
+
const relativeFilenames = mapToRelative(filenames);
|
|
21
|
+
const tsconfig = {
|
|
22
|
+
extends: './tsconfig.json',
|
|
23
|
+
files: relativeFilenames
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
FileSystem.writeFileSync(tsconfigPath, JSON.stringify(tsconfig));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return `tsc -p ${tsconfigPath}`;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export const defaultReleaseRules = [
|
|
2
|
+
{ breaking: true, release: 'major' },
|
|
3
|
+
{ type: 'feat', release: 'minor' },
|
|
4
|
+
{ type: 'fix', release: 'patch' },
|
|
5
|
+
{ type: 'perf', release: 'minor' },
|
|
6
|
+
{ type: 'deps', release: false },
|
|
7
|
+
{ type: 'deps', scope: 'prod', release: 'patch' },
|
|
8
|
+
{ type: 'refactor', release: false },
|
|
9
|
+
{ type: 'docs', release: false },
|
|
10
|
+
{ type: 'chore', release: false },
|
|
11
|
+
{ type: 'release', release: false },
|
|
12
|
+
{ type: 'revert', release: false },
|
|
13
|
+
{ type: 'style', release: false },
|
|
14
|
+
{ type: 'test', release: false }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const defaultChangelogTypes = [
|
|
18
|
+
{ type: 'feat', section: 'Features' },
|
|
19
|
+
{ type: 'fix', section: 'Fixes' },
|
|
20
|
+
{ type: 'perf', section: 'Performance improvements' },
|
|
21
|
+
{ type: 'deps', scope: 'prod', section: 'Dependency updates' }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const createReleaseConfig = ({
|
|
25
|
+
host,
|
|
26
|
+
owner,
|
|
27
|
+
repository,
|
|
28
|
+
releaseRules = defaultReleaseRules,
|
|
29
|
+
changelogTypes = defaultChangelogTypes
|
|
30
|
+
}) => ({
|
|
31
|
+
branches: ['main'],
|
|
32
|
+
repositoryUrl: `file://${process.cwd()}`,
|
|
33
|
+
preset: 'conventionalcommits',
|
|
34
|
+
dryRun: true,
|
|
35
|
+
|
|
36
|
+
plugins: [
|
|
37
|
+
[
|
|
38
|
+
'@semantic-release/commit-analyzer',
|
|
39
|
+
{
|
|
40
|
+
releaseRules
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'@semantic-release/release-notes-generator',
|
|
45
|
+
{
|
|
46
|
+
host,
|
|
47
|
+
presetConfig: {
|
|
48
|
+
types: changelogTypes
|
|
49
|
+
},
|
|
50
|
+
writerOpts: {
|
|
51
|
+
commitGroupsSort: ({ title: titleA }, { title: titleB }) => {
|
|
52
|
+
const indexA = changelogTypes.findIndex(({ section }) => section === titleA);
|
|
53
|
+
const indexB = changelogTypes.findIndex(({ section }) => section === titleB);
|
|
54
|
+
|
|
55
|
+
return indexA - indexB;
|
|
56
|
+
},
|
|
57
|
+
finalizeContext: (context) => {
|
|
58
|
+
const commitGroups = context.commitGroups.map((commitGroup) => ({
|
|
59
|
+
...commitGroup,
|
|
60
|
+
commits: commitGroup.commits.map((commit) => ({
|
|
61
|
+
...commit,
|
|
62
|
+
subject: commit.subject.replace(
|
|
63
|
+
`${host}/${context.owner}/${context.repository}`,
|
|
64
|
+
`${host}/${owner}/${repository}`
|
|
65
|
+
)
|
|
66
|
+
})),
|
|
67
|
+
collapse: commitGroup.title === 'Dependency updates'
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
...context,
|
|
72
|
+
owner,
|
|
73
|
+
repository,
|
|
74
|
+
commitGroups
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
mainTemplate: `{{> header}}
|
|
78
|
+
|
|
79
|
+
{{#if noteGroups}}
|
|
80
|
+
{{#each noteGroups}}
|
|
81
|
+
|
|
82
|
+
### ⚠ {{title}} !!
|
|
83
|
+
|
|
84
|
+
{{#each notes}}
|
|
85
|
+
* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}}
|
|
86
|
+
{{/each}}
|
|
87
|
+
{{/each}}
|
|
88
|
+
{{/if}}
|
|
89
|
+
{{#each commitGroups}}
|
|
90
|
+
|
|
91
|
+
{{#if title}}
|
|
92
|
+
### {{title}}
|
|
93
|
+
|
|
94
|
+
{{/if}}
|
|
95
|
+
{{#if collapse}}
|
|
96
|
+
<details>
|
|
97
|
+
<summary>Show all dependency updates</summary>
|
|
98
|
+
|
|
99
|
+
{{/if}}
|
|
100
|
+
{{#each commits}}
|
|
101
|
+
{{> commit root=@root}}
|
|
102
|
+
{{/each}}
|
|
103
|
+
{{#if collapse}}
|
|
104
|
+
|
|
105
|
+
</details>
|
|
106
|
+
{{/if}}
|
|
107
|
+
|
|
108
|
+
{{/each}}`
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export default createReleaseConfig({
|
|
116
|
+
host: 'https://github.com',
|
|
117
|
+
owner: 'cluerise',
|
|
118
|
+
repository: 'tools'
|
|
119
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"emitDecoratorMetadata": true,
|
|
4
|
+
"esModuleInterop": true,
|
|
5
|
+
"exactOptionalPropertyTypes": true,
|
|
6
|
+
"experimentalDecorators": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"jsx": "preserve",
|
|
9
|
+
"lib": ["dom", "esnext"],
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "node",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"noUncheckedIndexedAccess": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"baseUrl": "./",
|
|
17
|
+
"paths": {
|
|
18
|
+
":/*": ["./src/*/index"]
|
|
19
|
+
},
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"skipLibCheck": true,
|
|
22
|
+
"strict": true,
|
|
23
|
+
"target": "esnext",
|
|
24
|
+
"types": ["node", "vite/client"]
|
|
25
|
+
},
|
|
26
|
+
"exclude": ["node_modules"]
|
|
27
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import SemVer from "semver";
|
|
2
|
+
import FileSystem from "fs/promises";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { parse } from "smol-toml";
|
|
5
|
+
class ParseGitRepositoryError extends Error {
|
|
6
|
+
}
|
|
7
|
+
const gitProviderOrigins = {
|
|
8
|
+
github: "https://github.com"
|
|
9
|
+
};
|
|
10
|
+
const gitProviderNames = Object.keys(gitProviderOrigins);
|
|
11
|
+
const isGitProviderName = (name) => gitProviderNames.includes(name);
|
|
12
|
+
const parseRepositoryUrl = (urlString) => {
|
|
13
|
+
const urlValue = urlString.includes(":") ? urlString : `https://${urlString}`;
|
|
14
|
+
const url = new URL(urlValue);
|
|
15
|
+
const scheme = url.protocol.slice(0, -1);
|
|
16
|
+
if (isGitProviderName(scheme)) {
|
|
17
|
+
const [owner, repositoryName] = url.pathname.split("/");
|
|
18
|
+
if (!owner || !repositoryName) {
|
|
19
|
+
throw new ParseGitRepositoryError("Unknown owner or repositoryName");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
origin: gitProviderOrigins[scheme],
|
|
23
|
+
owner,
|
|
24
|
+
repositoryName
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (scheme === "https") {
|
|
28
|
+
const [, owner, repositoryName] = url.pathname.split("/");
|
|
29
|
+
if (!owner || !repositoryName) {
|
|
30
|
+
throw new ParseGitRepositoryError("Unknown owner or repositoryName");
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
origin: url.origin,
|
|
34
|
+
owner,
|
|
35
|
+
repositoryName: repositoryName.endsWith(".git") ? repositoryName.slice(0, -4) : repositoryName
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
throw new ParseGitRepositoryError("Unsupported repository URL");
|
|
39
|
+
};
|
|
40
|
+
const parsePackageJsonRepository = (repository) => {
|
|
41
|
+
if (typeof repository === "string") {
|
|
42
|
+
return parseRepositoryUrl(repository);
|
|
43
|
+
}
|
|
44
|
+
return parseRepositoryUrl(repository.url);
|
|
45
|
+
};
|
|
46
|
+
const enginesSchema = z.object({
|
|
47
|
+
node: z.string()
|
|
48
|
+
});
|
|
49
|
+
const repositoryObjectSchema = z.object({
|
|
50
|
+
type: z.string(),
|
|
51
|
+
url: z.string(),
|
|
52
|
+
directory: z.ostring()
|
|
53
|
+
});
|
|
54
|
+
const repositorySchema = z.union([z.string(), repositoryObjectSchema]);
|
|
55
|
+
const packageJsonSchema = z.object({
|
|
56
|
+
name: z.string(),
|
|
57
|
+
version: z.string(),
|
|
58
|
+
description: z.string(),
|
|
59
|
+
engines: enginesSchema.optional(),
|
|
60
|
+
repository: repositorySchema.optional()
|
|
61
|
+
});
|
|
62
|
+
const readPackageJson = async () => {
|
|
63
|
+
const packageJsonData = await FileSystem.readFile("package.json", { encoding: "utf8" });
|
|
64
|
+
const packageJson = JSON.parse(packageJsonData);
|
|
65
|
+
const parseResult = packageJsonSchema.safeParse(packageJson);
|
|
66
|
+
if (!parseResult.success) {
|
|
67
|
+
throw parseResult.error;
|
|
68
|
+
}
|
|
69
|
+
return packageJson;
|
|
70
|
+
};
|
|
71
|
+
const CoreCommands = {
|
|
72
|
+
readPackageJson,
|
|
73
|
+
parsePackageJsonRepository
|
|
74
|
+
};
|
|
75
|
+
const runMain = (main2) => {
|
|
76
|
+
main2(process.argv.slice(2)).then((exitCode) => {
|
|
77
|
+
process.exit(exitCode);
|
|
78
|
+
}).catch((error) => {
|
|
79
|
+
console.error(error);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
const herokuNodeReleasesUrl = "https://raw.githubusercontent.com/heroku/heroku-buildpack-nodejs/latest/inventory/node.toml";
|
|
84
|
+
const herokuNodeReleaseSchema = z.object({
|
|
85
|
+
version: z.string(),
|
|
86
|
+
channel: z.string(),
|
|
87
|
+
arch: z.string(),
|
|
88
|
+
url: z.string(),
|
|
89
|
+
etag: z.string()
|
|
90
|
+
});
|
|
91
|
+
const herokuNodeReleasesSchema = z.object({
|
|
92
|
+
name: z.string(),
|
|
93
|
+
releases: z.array(herokuNodeReleaseSchema)
|
|
94
|
+
});
|
|
95
|
+
const fetchNodeReleases = async () => {
|
|
96
|
+
const response = await fetch(herokuNodeReleasesUrl);
|
|
97
|
+
const data = await response.text();
|
|
98
|
+
const releasesData = parse(data);
|
|
99
|
+
const { releases } = herokuNodeReleasesSchema.parse(releasesData);
|
|
100
|
+
return releases;
|
|
101
|
+
};
|
|
102
|
+
const HerokuCommands = {
|
|
103
|
+
fetchNodeReleases
|
|
104
|
+
};
|
|
105
|
+
const getResultMessage = (nodeVersion, supported) => {
|
|
106
|
+
const nodeVersionIsRange = SemVer.valid(nodeVersion) === null;
|
|
107
|
+
const name = `Node.js version ${nodeVersionIsRange ? `range "${nodeVersion}"` : nodeVersion}`;
|
|
108
|
+
return `${name} is ${supported ? "" : "not "}supported on Heroku`;
|
|
109
|
+
};
|
|
110
|
+
const main = async () => {
|
|
111
|
+
var _a;
|
|
112
|
+
try {
|
|
113
|
+
const packageJson = await CoreCommands.readPackageJson();
|
|
114
|
+
const nodeVersion = (_a = packageJson.engines) == null ? void 0 : _a.node;
|
|
115
|
+
if (!nodeVersion) {
|
|
116
|
+
console.error("Error: Node.js version is not specified in package.json");
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
const nodeVersionIsValid = SemVer.validRange(nodeVersion) !== null;
|
|
120
|
+
if (!nodeVersionIsValid) {
|
|
121
|
+
console.error(`Error: Node.js version "${nodeVersion}" is not valid semver version or range`);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
const releases = await HerokuCommands.fetchNodeReleases();
|
|
125
|
+
const nodeVersionIsSupported = releases.some((release) => SemVer.satisfies(release.version, nodeVersion));
|
|
126
|
+
if (!nodeVersionIsSupported) {
|
|
127
|
+
console.error(`Error: ${getResultMessage(nodeVersion, false)}`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
console.log(`Info: ${getResultMessage(nodeVersion, true)}`);
|
|
131
|
+
return 0;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("Error: Cannot check Node.js version on Heroku");
|
|
134
|
+
console.error(error);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
runMain(main);
|