@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 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.`);
@@ -0,0 +1,11 @@
1
+ export default {
2
+ printWidth: 100,
3
+ tabWidth: 4,
4
+ useTabs: false,
5
+ semi: true,
6
+ singleQuote: false,
7
+ trailingComma: "es5",
8
+ bracketSpacing: true,
9
+ arrowParens: "avoid",
10
+ endOfLine: "lf",
11
+ };
@@ -0,0 +1,8 @@
1
+ import config from "@doikayt/typescript-build-config/eslint";
2
+
3
+ export default [
4
+ ...config,
5
+ {
6
+ ignores: ["dist/**", "node_modules/**"],
7
+ },
8
+ ];
@@ -0,0 +1,10 @@
1
+ .idea/
2
+
3
+ **/node_modules/
4
+ **/dist/
5
+ *.tsbuildinfo
6
+
7
+ coverage/
8
+ .nx/
9
+ .env
10
+
@@ -0,0 +1,5 @@
1
+ import config from "@doikayt/typescript-build-config/prettier";
2
+
3
+ export default {
4
+ ...config,
5
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src", "tests", "scripts/*.js", "scripts/*.ts", "*.config.js", "*.config.ts"]
4
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./node_modules/@doikayt/typescript-build-config/src/tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["vitest/globals"]
5
+ },
6
+ "include": ["src", "tests"]
7
+ }
@@ -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
+ }