@doikayt/typescript-build-config 0.1.9
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/LICENSE +21 -0
- package/README.md +280 -0
- package/package.json +60 -0
- package/src/eslint.js +48 -0
- package/src/pipeline/auto-changeset.sh +90 -0
- package/src/pipeline/changeset-config.json +11 -0
- package/src/pipeline/release.yml +72 -0
- package/src/pipeline/verify-npm-token.yml +17 -0
- package/src/postinstall.js +136 -0
- package/src/prettier.js +11 -0
- package/src/top-level/eslint.config.js +8 -0
- package/src/top-level/gitignore +10 -0
- package/src/top-level/prettier.config.js +5 -0
- package/src/top-level/tsconfig.eslint.json +4 -0
- package/src/top-level/tsconfig.json +8 -0
- package/src/top-level/tsconfig.test.json +7 -0
- package/src/tsconfig.base.json +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Data Lackey Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# @doikayt/typescript-build-config
|
|
2
|
+
|
|
3
|
+
Shared build configuration presets for TypeScript-based projects.
|
|
4
|
+
|
|
5
|
+
<!-- TOC:START -->
|
|
6
|
+
- [@doikayt/typescript-build-config](#doikayttypescript-build-config)
|
|
7
|
+
- [Purpose](#purpose)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Dependency Strategy](#dependency-strategy)
|
|
10
|
+
- [Current Contents](#current-contents)
|
|
11
|
+
- [Delivery Model](#delivery-model)
|
|
12
|
+
- [Release Pipeline](#release-pipeline)
|
|
13
|
+
- [Conventions Every Project Must Adhere To](#conventions-every-project-must-adhere-to)
|
|
14
|
+
- [The `ci` script](#the-ci-script)
|
|
15
|
+
- [NX projects](#nx-projects)
|
|
16
|
+
- [The `update-all-format` target](#the-update-all-format-target)
|
|
17
|
+
- [Publishing](#publishing)
|
|
18
|
+
- [For Maintainers](#for-maintainers)
|
|
19
|
+
- [Clone and install](#clone-and-install)
|
|
20
|
+
- [Run the full CI suite](#run-the-full-ci-suite)
|
|
21
|
+
- [License](#license)
|
|
22
|
+
<!-- TOC:END -->
|
|
23
|
+
|
|
24
|
+
## Purpose
|
|
25
|
+
|
|
26
|
+
This package centralises common build tooling configuration and release policy across all
|
|
27
|
+
TypeScript projects maintained under the `@doikayt` scope. The goal is a
|
|
28
|
+
single source of truth for such settings that should be held constant across
|
|
29
|
+
projects, avoiding drift between repos over time.
|
|
30
|
+
The plugin encapsulates common build **policy** and release **workflow logic**
|
|
31
|
+
via the pipeline files it installs into each consumer repo and the canonical release process in
|
|
32
|
+
[docs/RELEASE-PROCESS.md](docs/RELEASE-PROCESS.md).
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install --save-dev @doikayt/typescript-build-config
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The postinstall script copies starter config files into your project root and
|
|
41
|
+
sets up the release pipeline (see below).
|
|
42
|
+
|
|
43
|
+
## Dependency Strategy
|
|
44
|
+
|
|
45
|
+
`eslint` and `prettier` are listed as `dependencies` and are pulled in
|
|
46
|
+
automatically. The `@typescript-eslint/*` plugins are listed in both
|
|
47
|
+
`dependencies` and `peerDependencies` — the peer declaration pins the minimum
|
|
48
|
+
version to `^8.57.1` to avoid a `ts-api-utils` incompatibility with TS 5.4+.
|
|
49
|
+
|
|
50
|
+
This package targets new projects. Use in existing projects that pin older
|
|
51
|
+
versions of these tools may produce peer dependency conflicts.
|
|
52
|
+
|
|
53
|
+
## Current Contents
|
|
54
|
+
|
|
55
|
+
- ESLint config
|
|
56
|
+
- Prettier config
|
|
57
|
+
- TypeScript config (`tsconfig.json`, `tsconfig.test.json`, `tsconfig.eslint.json`)
|
|
58
|
+
- Postinstall script that copies starter top-level config files into your
|
|
59
|
+
project root, extending the configs installed under `node_modules`
|
|
60
|
+
- GitHub Actions release workflow (`.github/workflows/release.yml`)
|
|
61
|
+
- Changeset config (`.changeset/config.json`)
|
|
62
|
+
- Auto-changeset script (`scripts/auto-changeset.sh`)
|
|
63
|
+
- NPM token diagnostic workflow (`.github/workflows/verify-npm-token.yml`) — see
|
|
64
|
+
[Troubleshooting Publish Auth](docs/RELEASE-PROCESS.md#troubleshooting-publish-auth)
|
|
65
|
+
|
|
66
|
+
## Delivery Model
|
|
67
|
+
|
|
68
|
+
This package standardizes downstream repos through three delivery channels — script and
|
|
69
|
+
config artifacts that _implement_ the common policy, and a canonical document that
|
|
70
|
+
_states_ it:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌─────────────────────────────────────────────────┐
|
|
74
|
+
│ @doikayt/typescript-build-config │
|
|
75
|
+
│ │
|
|
76
|
+
│ presets src/eslint.js prettier tsconfig │
|
|
77
|
+
│ stubs src/top-level/* │
|
|
78
|
+
│ pipeline src/pipeline/* │
|
|
79
|
+
│ policy docs/RELEASE-PROCESS.md │
|
|
80
|
+
└─────────┬───────────────┬───────────────┬───────┘
|
|
81
|
+
│ │ │
|
|
82
|
+
1 REFERENCED │ 2 COPIED │ 3 LINKED, │
|
|
83
|
+
IN PLACE │ ON INSTALL │ NEVER COPIED │
|
|
84
|
+
│ │ │
|
|
85
|
+
presets stay in │ postinstall │ cited by URL │
|
|
86
|
+
node_modules — │ seeds stubs + │ only; one │
|
|
87
|
+
updates flow │ pipeline once;│ canonical │
|
|
88
|
+
with npm update │ consumer owns │ copy — drift │
|
|
89
|
+
│ them; drift ⇒ │ is impossible │
|
|
90
|
+
│ diff warning │ │
|
|
91
|
+
▼ ▼ ▼
|
|
92
|
+
┌─────────────────────────────────────────────────┐
|
|
93
|
+
│ consumer repo │
|
|
94
|
+
│ │
|
|
95
|
+
│ seeded stubs: eslint.config.js tsconfig.json │
|
|
96
|
+
│ │ prettier.config.js … │
|
|
97
|
+
│ └────extends────► presets living in │
|
|
98
|
+
│ node_modules (chan. 1) │
|
|
99
|
+
│ owned pipeline: .github/workflows/release.yml │
|
|
100
|
+
│ scripts/auto-changeset.sh │
|
|
101
|
+
│ CONTRIBUTING.md ─cites URL─► RELEASE-PROCESS.md│
|
|
102
|
+
└─────────────────────────────────────────────────┘
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Terms:**
|
|
106
|
+
|
|
107
|
+
- **Presets** — the real configuration content: the ESLint rules (`src/eslint.js`),
|
|
108
|
+
Prettier options, and base tsconfig this package exports. They stay inside
|
|
109
|
+
`node_modules` and are never copied — which is what makes them ecosystem-wide levers:
|
|
110
|
+
change a lint rule once here, publish, and every downstream repo receives the new rule
|
|
111
|
+
on its next `npm update`, with no per-repo edits.
|
|
112
|
+
- **Stubs** — thin files seeded once into the consumer's project root by the postinstall
|
|
113
|
+
script (`eslint.config.js`, `tsconfig.json`, …). Their only job is to `extends`/import
|
|
114
|
+
the presets: channel 2 seeds these static, copied-once files, and they point back at
|
|
115
|
+
the evolving content that channel 1 keeps current in `node_modules`.
|
|
116
|
+
- **Owned pipeline** — the release workflow and scripts, copied on install. "Owned" by
|
|
117
|
+
the **consumer repo**: it may edit its copies, upstream never overwrites them, and
|
|
118
|
+
postinstall prints a diff warning when a copy drifts from the canonical template.
|
|
119
|
+
- **Policy** — the release process itself, stated once in
|
|
120
|
+
[docs/RELEASE-PROCESS.md](docs/RELEASE-PROCESS.md). Consumers reference it by URL from
|
|
121
|
+
their own contributor docs (as the consumer box shows) — never copied, so it cannot
|
|
122
|
+
drift.
|
|
123
|
+
|
|
124
|
+
## Release Pipeline
|
|
125
|
+
|
|
126
|
+
On install, the postinstall script copies the release pipeline files into your
|
|
127
|
+
project automatically. The package name in `auto-changeset.sh` is substituted
|
|
128
|
+
from your project's `package.json` at install time.
|
|
129
|
+
|
|
130
|
+
On `npm update`, the behavior per file is:
|
|
131
|
+
|
|
132
|
+
- **File absent** — copied into place
|
|
133
|
+
- **File identical to canonical** — skipped silently
|
|
134
|
+
- **File differs from canonical** — warning printed with a diff; local copy is
|
|
135
|
+
left untouched for manual review
|
|
136
|
+
|
|
137
|
+
This means local customisations are never silently overwritten, but you are
|
|
138
|
+
notified when your copy has drifted from the upstream version.
|
|
139
|
+
|
|
140
|
+
The release pipeline requires an `NPM` secret stored at the GitHub organisation
|
|
141
|
+
level. All repos under the org inherit it automatically — no per-repo secret
|
|
142
|
+
configuration is needed.
|
|
143
|
+
|
|
144
|
+
## Conventions Every Project Must Adhere To
|
|
145
|
+
|
|
146
|
+
Every project that installs this package must implement two named entry points.
|
|
147
|
+
`postinstall` warns on every `npm install` or `npm update` until each is in
|
|
148
|
+
place.
|
|
149
|
+
|
|
150
|
+
### The `ci` script
|
|
151
|
+
|
|
152
|
+
Every project must expose a `ci` script in `package.json`. The release workflow
|
|
153
|
+
calls `npm run ci` as its CI gate. A project without a `ci` script will fail
|
|
154
|
+
the CI job — the correct signal that the convention has not been met.
|
|
155
|
+
|
|
156
|
+
`ci` is the single entry point for "everything that must pass before a release":
|
|
157
|
+
formatting checks, tests, linting — whatever the project requires. What it
|
|
158
|
+
calls internally is up to the project:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"scripts": { "ci": "prettier --check src/ && vitest run && playwright test" }
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`npm run ci` doubles as your local "simulate CI" command.
|
|
167
|
+
|
|
168
|
+
**Invariant:** if `npm run ci` passes locally, and you commit and push, the
|
|
169
|
+
remote CI job will pass.
|
|
170
|
+
|
|
171
|
+
#### NX projects
|
|
172
|
+
|
|
173
|
+
NX-based projects must still expose `ci` as a thin shim in `package.json`, even
|
|
174
|
+
if it only delegates to NX targets:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{ "scripts": { "ci": "nx run-many --target=ci --all" } }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The release workflow is NX-agnostic — it calls `npm run ci`, never NX directly.
|
|
181
|
+
|
|
182
|
+
### The `update-all-format` target
|
|
183
|
+
|
|
184
|
+
Every project must expose an `update-all-format` entry point — either a
|
|
185
|
+
`package.json` script or an NX `project.json` target. This is the single
|
|
186
|
+
command for "reformat everything before reviewing a diff": run it before
|
|
187
|
+
committing to keep diffs clean and reviewable.
|
|
188
|
+
|
|
189
|
+
The plugin enforces the name only, not the content:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"scripts": {
|
|
194
|
+
"update-all-format": "prettier --write src/ && npm run update-markdown-docs"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Or as an NX target:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"targets": {
|
|
204
|
+
"update-all-format": {
|
|
205
|
+
"executor": "nx:run-commands",
|
|
206
|
+
"options": { "command": "prettier --write src/" }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
If neither is found, `postinstall` prints a warning (non-fatal) on every
|
|
213
|
+
`npm install` or `npm update` until the target is added.
|
|
214
|
+
|
|
215
|
+
NX projects may define the target in `project.json` instead of `package.json`
|
|
216
|
+
scripts — the postinstall check recognises both. There is no shim requirement
|
|
217
|
+
for `update-all-format` (unlike `ci`), because no external tooling calls it
|
|
218
|
+
directly.
|
|
219
|
+
|
|
220
|
+
## Publishing
|
|
221
|
+
|
|
222
|
+
Releases are automated via Changesets and GitHub Actions. The full policy —
|
|
223
|
+
commit-prefix → bump mapping, forcing or suppressing a release, resolving
|
|
224
|
+
`changeset status` errors, verifying a release, troubleshooting publish auth —
|
|
225
|
+
is documented in [docs/RELEASE-PROCESS.md](docs/RELEASE-PROCESS.md). That
|
|
226
|
+
document is the canonical release policy for every repo that installs this
|
|
227
|
+
package.
|
|
228
|
+
|
|
229
|
+
Quick reference:
|
|
230
|
+
|
|
231
|
+
**For a patch release** — push a `fix:` commit to `main`. The workflow handles
|
|
232
|
+
the rest.
|
|
233
|
+
|
|
234
|
+
**For a minor or major release** — run `npx changeset` locally, choose the
|
|
235
|
+
bump level at the prompt, commit the generated `.changeset/*.md` file, then
|
|
236
|
+
push. The auto-generation step is skipped when a handwritten changeset is
|
|
237
|
+
present. For majors this is mandatory: a breaking-change commit (`feat!:` or
|
|
238
|
+
`BREAKING CHANGE` in the body) with no handwritten changeset fails the
|
|
239
|
+
release job until one is committed.
|
|
240
|
+
|
|
241
|
+
**Manual publish (emergency):** trigger the workflow manually via
|
|
242
|
+
**GitHub Actions → CI / Release → Run workflow** on the `main` branch.
|
|
243
|
+
|
|
244
|
+
## For Maintainers
|
|
245
|
+
|
|
246
|
+
### Clone and install
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
git clone git@github.com:doikayt/typescript-build-config.git
|
|
250
|
+
cd typescript-build-config
|
|
251
|
+
npm install
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The postinstall script detects that it is running inside the plugin repo itself
|
|
255
|
+
and exits immediately — no config stubs or pipeline files are copied.
|
|
256
|
+
|
|
257
|
+
### Run the full CI suite
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm run ci
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This is the same entry point mandated for every consumer repo. In this project
|
|
264
|
+
it runs a Prettier format check followed by the full test suite:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
npm run ci → prettier --check src/ → node --test
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
A passing `npm run ci` locally means the remote CI job will pass.
|
|
271
|
+
|
|
272
|
+
To auto-fix formatting before verifying:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm run update-all-format && npm run ci
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@doikayt/typescript-build-config",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "Shared build configuration for TypeScript projects — ESLint, and future tsconfig/Prettier presets",
|
|
5
|
+
"_comments": {
|
|
6
|
+
"exports": "This block lists the config files that will be extended by files in in src/toplevel - which are copied to consuming project root folder. An example: eslint.config.js imports from '@doikayt/typescript-build-config/eslint'.",
|
|
7
|
+
"files": "These are the files included in the package tarball. Dotfiles like .gitignore are excluded by npm by default and must be listed explicitly alongside src/."
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "src/eslint.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
"./eslint": "./src/eslint.js",
|
|
13
|
+
"./prettier": "./src/prettier.js",
|
|
14
|
+
"./tsconfig": "./src/tsconfig.base.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src/",
|
|
18
|
+
"src/top-level/.gitignore"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"prepack": "echo no build step required",
|
|
22
|
+
"postinstall": "node src/postinstall.js",
|
|
23
|
+
"update-all-format": "prettier --write src/ && update-markdown-toc README.md",
|
|
24
|
+
"check-format": "prettier --check src/ && update-markdown-toc --check README.md",
|
|
25
|
+
"ci": "npm run check-format && node --test",
|
|
26
|
+
"test": "node --test"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@changesets/cli": "^2.29.8",
|
|
36
|
+
"@datalackey/update-markdown-toc": "^1.4.14"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
40
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
41
|
+
"eslint": "^9.0.0",
|
|
42
|
+
"prettier": "^3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"eslint",
|
|
46
|
+
"typescript",
|
|
47
|
+
"config",
|
|
48
|
+
"build",
|
|
49
|
+
"tooling"
|
|
50
|
+
],
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+ssh://git@github.com/doikayt/typescript-build-config.git"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
|
58
|
+
"@typescript-eslint/parser": "^8.57.1"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/eslint.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
2
|
+
import tsparser from "@typescript-eslint/parser";
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
files: ["**/*.ts"],
|
|
7
|
+
languageOptions: {
|
|
8
|
+
parser: tsparser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
project: "./tsconfig.eslint.json",
|
|
11
|
+
sourceType: "module",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
plugins: {
|
|
15
|
+
"@typescript-eslint": tseslint,
|
|
16
|
+
},
|
|
17
|
+
rules: {
|
|
18
|
+
...tseslint.configs["recommended"].rules,
|
|
19
|
+
...tseslint.configs["recommended-requiring-type-checking"].rules,
|
|
20
|
+
eqeqeq: ["error", "always"],
|
|
21
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
22
|
+
"@typescript-eslint/no-unsafe-assignment": "error",
|
|
23
|
+
"@typescript-eslint/no-unsafe-call": "error",
|
|
24
|
+
"@typescript-eslint/no-unsafe-member-access": "error",
|
|
25
|
+
"@typescript-eslint/no-unsafe-return": "error",
|
|
26
|
+
"@typescript-eslint/strict-boolean-expressions": "error",
|
|
27
|
+
"@typescript-eslint/no-floating-promises": "error",
|
|
28
|
+
"prefer-const": "error",
|
|
29
|
+
"no-fallthrough": "error",
|
|
30
|
+
"@typescript-eslint/no-shadow": "error",
|
|
31
|
+
"@typescript-eslint/no-unused-vars": [
|
|
32
|
+
"error",
|
|
33
|
+
{
|
|
34
|
+
args: "all",
|
|
35
|
+
argsIgnorePattern: "^_",
|
|
36
|
+
varsIgnorePattern: "^_",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
"@typescript-eslint/explicit-function-return-type": [
|
|
40
|
+
"warn",
|
|
41
|
+
{
|
|
42
|
+
allowExpressions: true,
|
|
43
|
+
allowTypedFunctionExpressions: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Auto-generate a changeset from conventional commit messages when none exists.
|
|
3
|
+
# Run in CI before `npx changeset version`.
|
|
4
|
+
#
|
|
5
|
+
# Bump mapping (intentionally conservative — automated releases are always patch):
|
|
6
|
+
# fix:, feat:, perf: → patch
|
|
7
|
+
# feat!: / BREAKING CHANGE → FAIL: majors require a handwritten changeset
|
|
8
|
+
# chore:, ci:, docs:, refactor:, style:, test:, build: → no release
|
|
9
|
+
#
|
|
10
|
+
# Minor and major bumps must be declared explicitly via `npx changeset` before
|
|
11
|
+
# pushing. A breaking-change marker with no handwritten changeset fails the
|
|
12
|
+
# release job until a changeset (real or empty) is committed.
|
|
13
|
+
# If no releasable commits are found, exits cleanly — changeset version
|
|
14
|
+
# then has nothing to consume and publish is a no-op.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
CHANGESET_DIR=".changeset"
|
|
18
|
+
PACKAGES=("__PACKAGE_NAME__")
|
|
19
|
+
|
|
20
|
+
# Skip if a manually-authored changeset already exists
|
|
21
|
+
EXISTING=$(find "$CHANGESET_DIR" -name "*.md" ! -name "README.md" | wc -l)
|
|
22
|
+
if [ "$EXISTING" -gt 0 ]; then
|
|
23
|
+
echo "→ Changeset already present — skipping auto-generation"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Commits since last tag, or all commits if repo has no tags yet
|
|
28
|
+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
29
|
+
if [ -n "$LAST_TAG" ]; then
|
|
30
|
+
RANGE="${LAST_TAG}..HEAD"
|
|
31
|
+
else
|
|
32
|
+
RANGE="HEAD"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
SUBJECTS=$(git log "$RANGE" --pretty=format:"%s" 2>/dev/null || true)
|
|
36
|
+
FULL_LOG=$(git log "$RANGE" --pretty=format:"%s%n%b" 2>/dev/null || true)
|
|
37
|
+
|
|
38
|
+
if [ -z "$SUBJECTS" ]; then
|
|
39
|
+
echo "→ No commits in range — skipping auto-generation"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
BUMP=""
|
|
44
|
+
SUMMARY_LINES=()
|
|
45
|
+
|
|
46
|
+
while IFS= read -r line; do
|
|
47
|
+
[ -z "$line" ] && continue
|
|
48
|
+
if echo "$line" | grep -qE "^[a-z]+(\([^)]+\))?!:" || echo "$line" | grep -qE "^BREAKING CHANGE"; then
|
|
49
|
+
echo "ERROR: breaking-change commit found with no handwritten changeset:" >&2
|
|
50
|
+
echo " $line" >&2
|
|
51
|
+
echo "Major releases require explicit human declaration. Either:" >&2
|
|
52
|
+
echo " npx changeset # declare the bump, write migration notes" >&2
|
|
53
|
+
echo " npx changeset add --empty # or suppress the release entirely" >&2
|
|
54
|
+
echo "Commit the .changeset/*.md file and push again." >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
if echo "$line" | grep -qE "^(fix|feat|perf)(\([^)]+\))?:" && [ -z "$BUMP" ]; then
|
|
58
|
+
BUMP="patch"
|
|
59
|
+
fi
|
|
60
|
+
done <<< "$FULL_LOG"
|
|
61
|
+
|
|
62
|
+
while IFS= read -r line; do
|
|
63
|
+
[ -z "$line" ] && continue
|
|
64
|
+
if echo "$line" | grep -qE "^(fix|feat|perf)(\([^)]+\))?:"; then
|
|
65
|
+
SUMMARY_LINES+=("- $line")
|
|
66
|
+
fi
|
|
67
|
+
done <<< "$SUBJECTS"
|
|
68
|
+
|
|
69
|
+
if [ -z "$BUMP" ]; then
|
|
70
|
+
echo "→ No releasable commits (fix:/feat:/perf:) since ${LAST_TAG:-beginning} — skipping"
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
FRONTMATTER=""
|
|
75
|
+
for pkg in "${PACKAGES[@]}"; do
|
|
76
|
+
FRONTMATTER="${FRONTMATTER}\"${pkg}\": ${BUMP}"$'\n'
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
DESCRIPTION=$(printf '%s\n' "${SUMMARY_LINES[@]}")
|
|
80
|
+
FILENAME="${CHANGESET_DIR}/auto-$(date +%s)-$$.md"
|
|
81
|
+
|
|
82
|
+
cat > "$FILENAME" <<EOF
|
|
83
|
+
---
|
|
84
|
+
${FRONTMATTER}---
|
|
85
|
+
|
|
86
|
+
${DESCRIPTION}
|
|
87
|
+
EOF
|
|
88
|
+
|
|
89
|
+
echo "→ Generated ${FILENAME} (bump: ${BUMP})"
|
|
90
|
+
cat "$FILENAME"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "public",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: CI / Release
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: write
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
pull_request:
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ci:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v5
|
|
17
|
+
- uses: actions/setup-node@v5
|
|
18
|
+
with:
|
|
19
|
+
node-version: 22
|
|
20
|
+
cache: npm
|
|
21
|
+
- run: npm ci
|
|
22
|
+
- run: npm run ci
|
|
23
|
+
- name: Check Changesets (PR only)
|
|
24
|
+
if: github.event_name == 'pull_request'
|
|
25
|
+
run: npx changeset status
|
|
26
|
+
|
|
27
|
+
release:
|
|
28
|
+
needs: ci
|
|
29
|
+
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
|
30
|
+
permissions:
|
|
31
|
+
contents: write
|
|
32
|
+
id-token: write
|
|
33
|
+
concurrency:
|
|
34
|
+
group: release
|
|
35
|
+
cancel-in-progress: false
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v5
|
|
39
|
+
with:
|
|
40
|
+
fetch-depth: 0
|
|
41
|
+
- uses: actions/setup-node@v5
|
|
42
|
+
with:
|
|
43
|
+
node-version: 22
|
|
44
|
+
registry-url: https://registry.npmjs.org
|
|
45
|
+
cache: npm
|
|
46
|
+
- run: npm ci
|
|
47
|
+
- name: Configure git
|
|
48
|
+
run: |
|
|
49
|
+
git config user.name "github-actions[bot]"
|
|
50
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
51
|
+
- name: Auto-generate changeset from commits
|
|
52
|
+
run: bash scripts/auto-changeset.sh
|
|
53
|
+
- name: Apply version bumps
|
|
54
|
+
run: npx changeset version
|
|
55
|
+
- name: Commit and push version bumps
|
|
56
|
+
run: |
|
|
57
|
+
git add .
|
|
58
|
+
git diff --staged --quiet && echo "No changesets to release" || \
|
|
59
|
+
(git commit -m "chore: release [skip ci]" && git push)
|
|
60
|
+
- name: Publish
|
|
61
|
+
id: publish
|
|
62
|
+
run: npx changeset publish
|
|
63
|
+
env:
|
|
64
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM }}
|
|
65
|
+
- name: Push release tags
|
|
66
|
+
run: git push --tags
|
|
67
|
+
- name: Post-publish hook (optional)
|
|
68
|
+
if: steps.publish.outcome == 'success'
|
|
69
|
+
run: |
|
|
70
|
+
node -e "process.exit(require('./package.json').scripts?.['post-publish'] ? 0 : 1)" \
|
|
71
|
+
&& npm run post-publish \
|
|
72
|
+
|| echo "No post-publish script — skipping"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Verify NPM Token
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
verify-token:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/setup-node@v5
|
|
11
|
+
with:
|
|
12
|
+
node-version: 22
|
|
13
|
+
registry-url: https://registry.npmjs.org
|
|
14
|
+
- name: Check npm auth
|
|
15
|
+
run: npm whoami
|
|
16
|
+
env:
|
|
17
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM }}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
copyFileSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
} from "fs";
|
|
8
|
+
import { resolve, dirname } from "path";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import { tmpdir } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
|
|
13
|
+
const PREFIX = "[@doikayt/typescript-build-config]";
|
|
14
|
+
|
|
15
|
+
console.log(`${PREFIX} Running postinstall...`);
|
|
16
|
+
|
|
17
|
+
const projectRoot = process.env.INIT_CWD ?? process.cwd();
|
|
18
|
+
const projectPkg = JSON.parse(
|
|
19
|
+
readFileSync(resolve(projectRoot, "package.json"), "utf8"),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (projectPkg.name === "@doikayt/typescript-build-config") {
|
|
23
|
+
console.log(`${PREFIX} Self-install detected — postinstall is a no-op.`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const srcDir = new URL("top-level", import.meta.url).pathname;
|
|
28
|
+
const pipelineDir = new URL("pipeline", import.meta.url).pathname;
|
|
29
|
+
|
|
30
|
+
// --- Top-level config files: all-or-nothing ---
|
|
31
|
+
|
|
32
|
+
const configFiles = [
|
|
33
|
+
{ src: "gitignore", dest: ".gitignore" },
|
|
34
|
+
{ src: "tsconfig.json", dest: "tsconfig.json" },
|
|
35
|
+
{ src: "tsconfig.test.json", dest: "tsconfig.test.json" },
|
|
36
|
+
{ src: "tsconfig.eslint.json", dest: "tsconfig.eslint.json" },
|
|
37
|
+
{ src: "eslint.config.js", dest: "eslint.config.js" },
|
|
38
|
+
{ src: "prettier.config.js", dest: "prettier.config.js" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const alreadyExist = configFiles
|
|
42
|
+
.map(({ dest }) => resolve(projectRoot, dest))
|
|
43
|
+
.filter((destPath) => existsSync(destPath));
|
|
44
|
+
|
|
45
|
+
if (alreadyExist.length > 0) {
|
|
46
|
+
console.log(`${PREFIX} Skipping config files — the following already exist:`);
|
|
47
|
+
for (const destPath of alreadyExist) console.log(" " + destPath);
|
|
48
|
+
console.log(`${PREFIX} Delete them manually to re-run postinstall.`);
|
|
49
|
+
} else {
|
|
50
|
+
for (const { src, dest } of configFiles) {
|
|
51
|
+
const srcPath = resolve(srcDir, src);
|
|
52
|
+
const destPath = resolve(projectRoot, dest);
|
|
53
|
+
if (!existsSync(srcPath)) {
|
|
54
|
+
console.error(`${PREFIX} Missing source: ${srcPath}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
copyFileSync(srcPath, destPath);
|
|
58
|
+
console.log(`${PREFIX} Copied ${src} -> ${dest}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- Pipeline files: per-file copy/warn/diff ---
|
|
63
|
+
|
|
64
|
+
const projectName = projectPkg.name;
|
|
65
|
+
|
|
66
|
+
const pipelineFiles = [
|
|
67
|
+
{ src: "release.yml", dest: ".github/workflows/release.yml" },
|
|
68
|
+
{ src: "changeset-config.json", dest: ".changeset/config.json" },
|
|
69
|
+
{ src: "auto-changeset.sh", dest: "scripts/auto-changeset.sh" },
|
|
70
|
+
{
|
|
71
|
+
src: "verify-npm-token.yml",
|
|
72
|
+
dest: ".github/workflows/verify-npm-token.yml",
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
for (const { src, dest } of pipelineFiles) {
|
|
77
|
+
const srcPath = resolve(pipelineDir, src);
|
|
78
|
+
const destPath = resolve(projectRoot, dest);
|
|
79
|
+
|
|
80
|
+
let canonical = readFileSync(srcPath, "utf8");
|
|
81
|
+
|
|
82
|
+
if (src === "auto-changeset.sh") {
|
|
83
|
+
canonical = canonical.replace(
|
|
84
|
+
'PACKAGES=("__PACKAGE_NAME__")',
|
|
85
|
+
`PACKAGES=("${projectName}")`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!existsSync(destPath)) {
|
|
90
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
91
|
+
writeFileSync(destPath, canonical);
|
|
92
|
+
console.log(`${PREFIX} Copied ${src} -> ${dest}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const existing = readFileSync(destPath, "utf8");
|
|
97
|
+
if (existing === canonical) continue;
|
|
98
|
+
|
|
99
|
+
console.warn(
|
|
100
|
+
`${PREFIX} WARNING: ${dest} differs from canonical version — skipping. Review the diff:`,
|
|
101
|
+
);
|
|
102
|
+
const tmpPath = join(tmpdir(), `tbc-canonical-${src}`);
|
|
103
|
+
writeFileSync(tmpPath, canonical);
|
|
104
|
+
try {
|
|
105
|
+
execSync(`diff "${tmpPath}" "${destPath}"`, { stdio: "inherit" });
|
|
106
|
+
} catch {
|
|
107
|
+
// diff exits 1 when files differ — output already printed via stdio: inherit
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- update-all-format convention check ---
|
|
112
|
+
|
|
113
|
+
let hasUpdateAllFormat = !!projectPkg.scripts?.["update-all-format"];
|
|
114
|
+
|
|
115
|
+
if (!hasUpdateAllFormat) {
|
|
116
|
+
const projectJsonPath = resolve(projectRoot, "project.json");
|
|
117
|
+
if (existsSync(projectJsonPath)) {
|
|
118
|
+
try {
|
|
119
|
+
const projectJson = JSON.parse(readFileSync(projectJsonPath, "utf8"));
|
|
120
|
+
if (projectJson.targets?.["update-all-format"]) hasUpdateAllFormat = true;
|
|
121
|
+
} catch {
|
|
122
|
+
// malformed project.json — treat as absent
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!hasUpdateAllFormat) {
|
|
128
|
+
console.warn(`${PREFIX} Missing target: update-all-format`);
|
|
129
|
+
console.warn(`Add to package.json scripts:`);
|
|
130
|
+
console.warn(
|
|
131
|
+
` "update-all-format": "npm run format && npm run update-markdown-docs"`,
|
|
132
|
+
);
|
|
133
|
+
console.warn(`Or add an NX target with the same name.`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(`${PREFIX} Postinstall complete.`);
|
package/src/prettier.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmitOnError": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"noUnusedLocals": true,
|
|
13
|
+
"noUnusedParameters": true
|
|
14
|
+
}
|
|
15
|
+
}
|