@integr8-media/config 0.0.0-stage → 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mina Louis Leon
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 CHANGED
@@ -1,3 +1,222 @@
1
- # Temporary Holding Version
1
+ # @integr8-media/config
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared tooling presets for every `@integr8-media` repo: TypeScript, Biome, Vitest, tsdown, Lefthook,
4
+ size-limit and publint, plus reusable GitHub Actions workflows. No runtime code.
5
+
6
+ The full spec is in [docs/SPEC.md](docs/SPEC.md).
7
+
8
+ ## Requirements
9
+
10
+ - Node 24 LTS
11
+ - TypeScript 5.7 or newer (the presets target ES2024, which TypeScript added in 5.7)
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install --save-dev @integr8-media/config
17
+ ```
18
+
19
+ Then install the tools you use (all are optional peer dependencies):
20
+
21
+ ```sh
22
+ npm install --save-dev typescript @biomejs/biome vitest @vitest/coverage-v8 tsdown lefthook \
23
+ size-limit @size-limit/file publint @arethetypeswrong/cli @types/node
24
+ ```
25
+
26
+ ## Set up a new package repo
27
+
28
+ ### TypeScript
29
+
30
+ ```jsonc
31
+ // tsconfig.json
32
+ { "extends": "@integr8-media/config/tsconfig/react-library.json", "include": ["src"] }
33
+ ```
34
+
35
+ | Preset | Use for |
36
+ | --- | --- |
37
+ | `@integr8-media/config/tsconfig/library.json` | Node or universal library: NodeNext, declarations, isolatedDeclarations |
38
+ | `@integr8-media/config/tsconfig/react-library.json` | React library: Bundler resolution, DOM types, react-jsx, isolatedDeclarations |
39
+ | `@integr8-media/config/tsconfig/node-app.json` | Node application: NodeNext, no declarations, Node types |
40
+ | `@integr8-media/config/tsconfig/react-app.json` | React application: Bundler, DOM, noEmit |
41
+
42
+ All presets use ES2024, `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`,
43
+ `noImplicitOverride`, `noFallthroughCasesInSwitch`, `verbatimModuleSyntax`,
44
+ `resolveJsonModule`, `forceConsistentCasingInFileNames` and `skipLibCheck`.
45
+
46
+ Notes:
47
+
48
+ - `node-app.json` loads Node's types (`"types": ["node"]`), so install `@types/node`. A Node
49
+ library that needs Node's types adds `"compilerOptions": { "types": ["node"] }` itself;
50
+ `library.json` leaves them out so it also works for universal libraries.
51
+ - With `isolatedDeclarations`, keep `*.config.ts` files out of `include` (their
52
+ `export default someCall()` has no explicit type).
53
+
54
+ ### Biome
55
+
56
+ ```jsonc
57
+ // biome.json
58
+ { "extends": ["@integr8-media/config/biome"] }
59
+ ```
60
+
61
+ - Formatter: 2-space indent, line width 100, single quotes in TS, double quotes in JSX,
62
+ semicolons always, trailing commas everywhere.
63
+ - Linter: recommended rules, plus as errors: no default exports (allowed in `*.config.ts`),
64
+ camelCase or PascalCase file names, no explicit `any`, `import type` for type-only imports,
65
+ no non-null assertions. `console` is a warning.
66
+ - Ignores `dist/`, `coverage/`, `playground/dist/` and Markdown files (Biome 2.6 formats
67
+ Markdown, which would rewrite docs such as `docs/SPEC.md`). Needs Biome 2.6 or newer.
68
+ - If your `biome.json` sets `files.includes`, Biome adds it to the preset's list, so list only
69
+ your extra ignores (for example `"!!.vscode"`) and don't repeat `"**"`.
70
+
71
+ ### Vitest
72
+
73
+ ```ts
74
+ // vitest.config.ts
75
+ import { integr8Vitest } from '@integr8-media/config/vitest';
76
+
77
+ export default integr8Vitest({ environment: 'node', coverageThreshold: 80 });
78
+ ```
79
+
80
+ v8 coverage on `src/**` (tests, `src/index.ts` and `playground/` excluded). The run fails when
81
+ line coverage is below the threshold. `environment: 'jsdom'` needs `jsdom` installed. To add
82
+ your own settings, wrap it with `mergeConfig` from `vitest/config`.
83
+
84
+ ### tsdown
85
+
86
+ ```ts
87
+ // tsdown.config.ts
88
+ import { integr8Tsdown } from '@integr8-media/config/tsdown';
89
+
90
+ export default integr8Tsdown({ entry: 'src/index.ts', platform: 'browser' });
91
+ ```
92
+
93
+ ESM only, `.d.ts` generation (fast through isolatedDeclarations), no source maps, clean output,
94
+ tree-shaking. Target `node24` for `platform: 'node'`, `es2024` otherwise. Dependencies and peer
95
+ dependencies stay external. Files are always named `.mjs` / `.d.mts`, so point `exports` there:
96
+
97
+ ```json
98
+ "exports": {
99
+ ".": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }
100
+ }
101
+ ```
102
+
103
+ ### size-limit
104
+
105
+ ```ts
106
+ // .size-limit.ts
107
+ import { integr8SizeLimit } from '@integr8-media/config/size-limit';
108
+
109
+ export default integr8SizeLimit([{ path: 'dist/index.mjs', limit: '10 kB' }]);
110
+ ```
111
+
112
+ Each entry takes `path`, `limit`, and optionally `name` and `import`. size-limit needs a
113
+ plugin: `@size-limit/file` measures built files; `@size-limit/esbuild` is needed for `import`.
114
+
115
+ ### Lefthook
116
+
117
+ ```yaml
118
+ # lefthook.yml
119
+ extends:
120
+ - node_modules/@integr8-media/config/lefthook/base.yml
121
+ ```
122
+
123
+ - pre-commit: `biome check --write` on staged files, fixes re-staged.
124
+ - pre-push, in order, stopping at the first failure: `typecheck`, `test:coverage`, `build`, and
125
+ `playground:smoke` when that script exists.
126
+
127
+ Add `"prepare": "lefthook install"` to your scripts so hooks are installed after `npm install`.
128
+
129
+ ### Standard scripts
130
+
131
+ ```json
132
+ "scripts": {
133
+ "lint": "biome check",
134
+ "format": "biome format --write",
135
+ "typecheck": "tsc --noEmit",
136
+ "test": "vitest run",
137
+ "test:coverage": "vitest run --coverage",
138
+ "build": "tsdown",
139
+ "playground": "ladle serve",
140
+ "playground:smoke": "playwright test",
141
+ "size": "size-limit",
142
+ "check:exports": "publint && attw --pack --profile esm-only"
143
+ }
144
+ ```
145
+
146
+ `--profile esm-only` tells attw not to report the CommonJS problems that every ESM-only package
147
+ has.
148
+
149
+ ### GitHub Actions
150
+
151
+ The workflows live in this GitHub repo, not in the npm package. Because the repo is private, its
152
+ **Settings → Actions → General → Access** must allow access from your other repositories.
153
+
154
+ ```yaml
155
+ # .github/workflows/ci.yml
156
+ on:
157
+ pull_request:
158
+ branches: [dev]
159
+ jobs:
160
+ checks:
161
+ uses: <owner>/integr8-config/.github/workflows/pr-checks.yml@v1
162
+ with:
163
+ os-matrix: false
164
+ ```
165
+
166
+ ```yaml
167
+ # .github/workflows/release.yml
168
+ on:
169
+ push:
170
+ tags: ["v*.*.*"]
171
+ jobs:
172
+ publish:
173
+ uses: <owner>/integr8-config/.github/workflows/publish.yml@v1
174
+ with:
175
+ provenance: false # true only for public repos (then also grant id-token: write)
176
+ secrets:
177
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
178
+ ```
179
+
180
+ - `pr-checks.yml`: install, lint, typecheck, test:coverage, build, size, check:exports.
181
+ `os-matrix: true` runs on Linux, macOS and Windows.
182
+ - `publish.yml`: checks the tag is `vX.Y.Z`, is on main and matches `package.json`; then
183
+ install, build, test and `npm publish --access public`.
184
+
185
+ ## API
186
+
187
+ | Import | Exports |
188
+ | --- | --- |
189
+ | `@integr8-media/config/vitest` | `integr8Vitest(options?)`, `ConfigError` |
190
+ | `@integr8-media/config/tsdown` | `integr8Tsdown(options)`, `ConfigError` |
191
+ | `@integr8-media/config/size-limit` | `integr8SizeLimit(entries)`, `ConfigError` |
192
+
193
+ Invalid options throw `ConfigError` with `code: 'INVALID_OPTION'`. The API reference is
194
+ generated from TSDoc with `npm run docs` (output in `docs/api/`).
195
+
196
+ ## Non-goals
197
+
198
+ - No runtime code and nothing app-specific
199
+ - No ESLint or Prettier presets
200
+ - No versioning tool (versions are bumped by hand)
201
+
202
+ ## Working on this repo
203
+
204
+ ```sh
205
+ npm install
206
+ npm run lint
207
+ npm run typecheck
208
+ npm run test:coverage # unit tests + integration tests that run each tool on fixtures/
209
+ npm run build
210
+ npm run size
211
+ npm run check:exports
212
+ ```
213
+
214
+ - Unit tests sit next to the code in `src/`.
215
+ - `test/` holds integration tests that run tsc, Biome, tsdown, Vitest, publint, attw and
216
+ Lefthook against `fixtures/`. They need `git` on the PATH, and on Windows Lefthook needs Git
217
+ Bash's `sh`.
218
+ - The React fixtures use a small JSX type stub instead of installing React.
219
+
220
+ ## License
221
+
222
+ MIT
@@ -0,0 +1,61 @@
1
+ {
2
+ "files": {
3
+ "includes": ["**", "!**/dist", "!**/coverage", "!**/playground/dist", "!**/*.md"]
4
+ },
5
+ "formatter": {
6
+ "enabled": true,
7
+ "indentStyle": "space",
8
+ "indentWidth": 2,
9
+ "lineWidth": 100
10
+ },
11
+ "javascript": {
12
+ "formatter": {
13
+ "quoteStyle": "single",
14
+ "jsxQuoteStyle": "double",
15
+ "semicolons": "always",
16
+ "trailingCommas": "all"
17
+ }
18
+ },
19
+ "linter": {
20
+ "enabled": true,
21
+ "rules": {
22
+ "preset": "recommended",
23
+ "style": {
24
+ "noDefaultExport": "error",
25
+ "noNonNullAssertion": "error",
26
+ "useImportType": "error",
27
+ "useFilenamingConvention": {
28
+ "level": "error",
29
+ "options": {
30
+ "filenameCases": ["camelCase", "PascalCase"],
31
+ "requireAscii": true
32
+ }
33
+ }
34
+ },
35
+ "suspicious": {
36
+ "noExplicitAny": "error",
37
+ "noConsole": "warn"
38
+ }
39
+ }
40
+ },
41
+ "assist": {
42
+ "enabled": true,
43
+ "actions": {
44
+ "source": {
45
+ "organizeImports": "on"
46
+ }
47
+ }
48
+ },
49
+ "overrides": [
50
+ {
51
+ "includes": ["**/*.config.ts", "**/*.config.mts", "**/*.config.js", "**/*.config.mjs"],
52
+ "linter": {
53
+ "rules": {
54
+ "style": {
55
+ "noDefaultExport": "off"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ]
61
+ }
@@ -0,0 +1,31 @@
1
+ // src/errors/configError.ts
2
+ /**
3
+ * Thrown by the preset functions when they receive options they cannot turn into a config.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * try {
8
+ * integr8Vitest({ coverageThreshold: 120 });
9
+ * } catch (error) {
10
+ * if (error instanceof ConfigError && error.code === 'INVALID_OPTION') {
11
+ * // handle it
12
+ * }
13
+ * }
14
+ * ```
15
+ */
16
+ var ConfigError = class extends Error {
17
+ /** Always `'ConfigError'`. */
18
+ name = "ConfigError";
19
+ /** Machine-readable reason for the error. */
20
+ code;
21
+ /**
22
+ * @param code - Machine-readable reason for the error.
23
+ * @param message - Human-readable description.
24
+ * @param options - Standard error options, such as `cause`.
25
+ */
26
+ constructor(code, message, options) {
27
+ super(message, options);
28
+ this.code = code;
29
+ }
30
+ };
31
+ export { ConfigError as t };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Codes carried by {@link ConfigError}.
3
+ *
4
+ * - `INVALID_OPTION`: an option passed to a preset function is missing, has the wrong type,
5
+ * or is out of range.
6
+ */
7
+ type ConfigErrorCode = "INVALID_OPTION";
8
+ /**
9
+ * Thrown by the preset functions when they receive options they cannot turn into a config.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * try {
14
+ * integr8Vitest({ coverageThreshold: 120 });
15
+ * } catch (error) {
16
+ * if (error instanceof ConfigError && error.code === 'INVALID_OPTION') {
17
+ * // handle it
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ declare class ConfigError extends Error {
23
+ /** Always `'ConfigError'`. */
24
+ override readonly name: "ConfigError";
25
+ /** Machine-readable reason for the error. */
26
+ readonly code: ConfigErrorCode;
27
+ /**
28
+ * @param code - Machine-readable reason for the error.
29
+ * @param message - Human-readable description.
30
+ * @param options - Standard error options, such as `cause`.
31
+ */
32
+ constructor(code: ConfigErrorCode, message: string, options?: ErrorOptions);
33
+ }
34
+ export { ConfigErrorCode as n, ConfigError as t };
@@ -0,0 +1,34 @@
1
+ import { n as ConfigErrorCode, t as ConfigError } from "./configError-DPT5pJ9Z.mjs";
2
+ import { SizeLimitConfig } from "size-limit";
3
+ /** One file (or group of files) to measure. */
4
+ interface Integr8SizeLimitEntry {
5
+ /** Built file(s) to measure, for example `'dist/index.mjs'`. */
6
+ path: string | string[];
7
+ /** Largest allowed size, for example `'5 kB'`. */
8
+ limit: string;
9
+ /** Label in the report. Defaults to the path. */
10
+ name?: string;
11
+ /**
12
+ * Named imports to measure instead of the whole file (tree-shaking check), for example
13
+ * `'{ Button }'`. Needs a bundler plugin such as `@size-limit/esbuild`.
14
+ */
15
+ import?: string | Record<string, string>;
16
+ }
17
+ /**
18
+ * Builds size-limit checks from a short list of entries. Each entry gets a readable name
19
+ * (the path when none is given) and is validated before size-limit runs.
20
+ *
21
+ * @param entries - Files to measure and their limits. At least one is required.
22
+ * @returns size-limit checks to default-export from `.size-limit.js` (or `.size-limit.ts`).
23
+ * @throws {@link ConfigError} with code `INVALID_OPTION` for an empty list, a missing path or
24
+ * an invalid limit.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // .size-limit.ts
29
+ * import { integr8SizeLimit } from '@integr8-media/config/size-limit';
30
+ * export default integr8SizeLimit([{ path: 'dist/index.mjs', limit: '10 kB' }]);
31
+ * ```
32
+ */
33
+ export declare function integr8SizeLimit(entries: readonly Integr8SizeLimitEntry[]): SizeLimitConfig;
34
+ export { ConfigError, type ConfigErrorCode, type Integr8SizeLimitEntry };
@@ -0,0 +1,39 @@
1
+ import { t as ConfigError } from "./configError-Cap5gwBB.mjs";
2
+ // src/sizeLimit/integr8SizeLimit.ts
3
+ const LIMIT_PATTERN = /^\d+(\.\d+)?\s*[a-z]+$/i;
4
+ /**
5
+ * Builds size-limit checks from a short list of entries. Each entry gets a readable name
6
+ * (the path when none is given) and is validated before size-limit runs.
7
+ *
8
+ * @param entries - Files to measure and their limits. At least one is required.
9
+ * @returns size-limit checks to default-export from `.size-limit.js` (or `.size-limit.ts`).
10
+ * @throws {@link ConfigError} with code `INVALID_OPTION` for an empty list, a missing path or
11
+ * an invalid limit.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // .size-limit.ts
16
+ * import { integr8SizeLimit } from '@integr8-media/config/size-limit';
17
+ * export default integr8SizeLimit([{ path: 'dist/index.mjs', limit: '10 kB' }]);
18
+ * ```
19
+ */
20
+ function integr8SizeLimit(entries) {
21
+ if (!Array.isArray(entries) || entries.length === 0) throw new ConfigError("INVALID_OPTION", "integr8SizeLimit: \"entries\" must be a non-empty list.");
22
+ return entries.map((entry, index) => toCheck(entry, index));
23
+ }
24
+ function toCheck(entry, index) {
25
+ const where = `integr8SizeLimit: entry ${index}`;
26
+ if (typeof entry !== "object" || entry === null) throw new ConfigError("INVALID_OPTION", `${where} must be an object.`);
27
+ const paths = typeof entry.path === "string" ? [entry.path] : entry.path;
28
+ if (!Array.isArray(paths) || paths.length === 0 || !paths.every((path) => typeof path === "string" && path.trim() !== "")) throw new ConfigError("INVALID_OPTION", `${where}: "path" must be a non-empty path or list of paths.`);
29
+ if (typeof entry.limit !== "string" || !LIMIT_PATTERN.test(entry.limit.trim())) throw new ConfigError("INVALID_OPTION", `${where}: "limit" must look like "10 kB"; got ${String(entry.limit)}.`);
30
+ if (entry.name !== void 0 && (typeof entry.name !== "string" || entry.name.trim() === "")) throw new ConfigError("INVALID_OPTION", `${where}: "name" must be a non-empty string.`);
31
+ const check = {
32
+ name: entry.name ?? paths.join(", "),
33
+ path: typeof entry.path === "string" ? entry.path : [...paths],
34
+ limit: entry.limit.trim()
35
+ };
36
+ if (entry.import !== void 0) check.import = typeof entry.import === "string" ? entry.import : { ...entry.import };
37
+ return check;
38
+ }
39
+ export { ConfigError, integr8SizeLimit };
@@ -0,0 +1,35 @@
1
+ import { n as ConfigErrorCode, t as ConfigError } from "./configError-DPT5pJ9Z.mjs";
2
+ import { UserConfig } from "tsdown";
3
+ /** Platforms the tsdown preset can build for. */
4
+ type Integr8TsdownPlatform = "node" | "browser" | "neutral";
5
+ /** Entry points: one path, a list of paths, or a map of output name to path. */
6
+ type Integr8TsdownEntry = string | string[] | Record<string, string>;
7
+ /** Options for {@link integr8Tsdown}. */
8
+ interface Integr8TsdownOptions {
9
+ /** Entry file(s) to build. */
10
+ entry: Integr8TsdownEntry;
11
+ /** Target platform. `node` builds for `node24`; the others build for `es2024`. */
12
+ platform: Integr8TsdownPlatform;
13
+ }
14
+ /**
15
+ * Builds the shared tsdown config: ESM only, `.d.ts` generation (fast path through
16
+ * isolatedDeclarations), no source maps, a clean output folder and tree-shaking.
17
+ *
18
+ * Output files always use `.mjs` and `.d.mts`, whatever the platform, so a package's
19
+ * `exports` map is the same everywhere. Dependencies and peer dependencies stay external
20
+ * (tsdown's default).
21
+ *
22
+ * @param options - Entry points and target platform.
23
+ * @returns A tsdown config to default-export from `tsdown.config.ts`.
24
+ * @throws {@link ConfigError} with code `INVALID_OPTION` for an empty entry or an unknown
25
+ * platform.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // tsdown.config.ts
30
+ * import { integr8Tsdown } from '@integr8-media/config/tsdown';
31
+ * export default integr8Tsdown({ entry: 'src/index.ts', platform: 'browser' });
32
+ * ```
33
+ */
34
+ export declare function integr8Tsdown(options: Integr8TsdownOptions): UserConfig;
35
+ export { ConfigError, type ConfigErrorCode, type Integr8TsdownEntry, type Integr8TsdownOptions, type Integr8TsdownPlatform };
@@ -0,0 +1,66 @@
1
+ import { t as ConfigError } from "./configError-Cap5gwBB.mjs";
2
+ // src/tsdown/integr8Tsdown.ts
3
+ const PLATFORMS = [
4
+ "node",
5
+ "browser",
6
+ "neutral"
7
+ ];
8
+ /**
9
+ * Builds the shared tsdown config: ESM only, `.d.ts` generation (fast path through
10
+ * isolatedDeclarations), no source maps, a clean output folder and tree-shaking.
11
+ *
12
+ * Output files always use `.mjs` and `.d.mts`, whatever the platform, so a package's
13
+ * `exports` map is the same everywhere. Dependencies and peer dependencies stay external
14
+ * (tsdown's default).
15
+ *
16
+ * @param options - Entry points and target platform.
17
+ * @returns A tsdown config to default-export from `tsdown.config.ts`.
18
+ * @throws {@link ConfigError} with code `INVALID_OPTION` for an empty entry or an unknown
19
+ * platform.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // tsdown.config.ts
24
+ * import { integr8Tsdown } from '@integr8-media/config/tsdown';
25
+ * export default integr8Tsdown({ entry: 'src/index.ts', platform: 'browser' });
26
+ * ```
27
+ */
28
+ function integr8Tsdown(options) {
29
+ if (typeof options !== "object" || options === null) throw new ConfigError("INVALID_OPTION", "integr8Tsdown: options are required.");
30
+ const { platform } = options;
31
+ if (!PLATFORMS.includes(platform)) throw new ConfigError("INVALID_OPTION", `integr8Tsdown: "platform" must be one of ${PLATFORMS.join(", ")}; got ${String(platform)}.`);
32
+ return {
33
+ entry: copyEntry(options.entry),
34
+ platform,
35
+ target: platform === "node" ? "node24" : "es2024",
36
+ format: "esm",
37
+ fixedExtension: true,
38
+ dts: true,
39
+ sourcemap: false,
40
+ clean: true,
41
+ treeshake: true
42
+ };
43
+ }
44
+ function copyEntry(entry) {
45
+ if (typeof entry === "string") {
46
+ if (entry.trim() === "") throw invalidEntry();
47
+ return entry;
48
+ }
49
+ if (Array.isArray(entry)) {
50
+ if (entry.length === 0 || !entry.every(isNonEmptyString)) throw invalidEntry();
51
+ return [...entry];
52
+ }
53
+ if (typeof entry === "object" && entry !== null) {
54
+ const values = Object.values(entry);
55
+ if (values.length === 0 || !values.every(isNonEmptyString)) throw invalidEntry();
56
+ return { ...entry };
57
+ }
58
+ throw invalidEntry();
59
+ }
60
+ function isNonEmptyString(value) {
61
+ return typeof value === "string" && value.trim() !== "";
62
+ }
63
+ function invalidEntry() {
64
+ return new ConfigError("INVALID_OPTION", "integr8Tsdown: \"entry\" must be a non-empty path, a non-empty list of paths, or a non-empty map of name to path.");
65
+ }
66
+ export { ConfigError, integr8Tsdown };
@@ -0,0 +1,29 @@
1
+ import { n as ConfigErrorCode, t as ConfigError } from "./configError-DPT5pJ9Z.mjs";
2
+ import { ViteUserConfig } from "vitest/config";
3
+ /** Test environments the Vitest preset supports. */
4
+ type Integr8VitestEnvironment = "node" | "jsdom";
5
+ /** Options for {@link integr8Vitest}. */
6
+ interface Integr8VitestOptions {
7
+ /** Test environment. Defaults to `'node'`. `'jsdom'` needs the `jsdom` package installed. */
8
+ environment?: Integr8VitestEnvironment;
9
+ /** Minimum line coverage in percent (0–100). Defaults to `80`. */
10
+ coverageThreshold?: number;
11
+ }
12
+ /**
13
+ * Builds the shared Vitest config: v8 coverage on `src/**` (tests, `src/index.ts` and
14
+ * `playground/` excluded) that fails when line coverage is below the threshold.
15
+ *
16
+ * @param options - Environment and coverage threshold.
17
+ * @returns A Vitest config to default-export from `vitest.config.ts`.
18
+ * @throws {@link ConfigError} with code `INVALID_OPTION` for an unknown environment or a
19
+ * threshold outside 0–100.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // vitest.config.ts
24
+ * import { integr8Vitest } from '@integr8-media/config/vitest';
25
+ * export default integr8Vitest({ environment: 'jsdom' });
26
+ * ```
27
+ */
28
+ export declare function integr8Vitest(options?: Integr8VitestOptions): ViteUserConfig;
29
+ export { ConfigError, type ConfigErrorCode, type Integr8VitestEnvironment, type Integr8VitestOptions };
@@ -0,0 +1,42 @@
1
+ import { t as ConfigError } from "./configError-Cap5gwBB.mjs";
2
+ // src/vitest/integr8Vitest.ts
3
+ const ENVIRONMENTS = ["node", "jsdom"];
4
+ const DEFAULT_COVERAGE_THRESHOLD = 80;
5
+ /**
6
+ * Builds the shared Vitest config: v8 coverage on `src/**` (tests, `src/index.ts` and
7
+ * `playground/` excluded) that fails when line coverage is below the threshold.
8
+ *
9
+ * @param options - Environment and coverage threshold.
10
+ * @returns A Vitest config to default-export from `vitest.config.ts`.
11
+ * @throws {@link ConfigError} with code `INVALID_OPTION` for an unknown environment or a
12
+ * threshold outside 0–100.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // vitest.config.ts
17
+ * import { integr8Vitest } from '@integr8-media/config/vitest';
18
+ * export default integr8Vitest({ environment: 'jsdom' });
19
+ * ```
20
+ */
21
+ function integr8Vitest(options = {}) {
22
+ const environment = options.environment ?? "node";
23
+ const coverageThreshold = options.coverageThreshold ?? DEFAULT_COVERAGE_THRESHOLD;
24
+ if (!ENVIRONMENTS.includes(environment)) throw new ConfigError("INVALID_OPTION", `integr8Vitest: "environment" must be one of ${ENVIRONMENTS.join(", ")}; got ${String(environment)}.`);
25
+ if (typeof coverageThreshold !== "number" || !Number.isFinite(coverageThreshold) || coverageThreshold < 0 || coverageThreshold > 100) throw new ConfigError("INVALID_OPTION", `integr8Vitest: "coverageThreshold" must be a number from 0 to 100; got ${String(coverageThreshold)}.`);
26
+ return { test: {
27
+ environment,
28
+ coverage: {
29
+ provider: "v8",
30
+ include: ["src/**/*.{ts,tsx}"],
31
+ exclude: [
32
+ "src/**/*.test.ts",
33
+ "src/**/*.test.tsx",
34
+ "src/**/*.d.ts",
35
+ "src/index.ts",
36
+ "playground/**"
37
+ ],
38
+ thresholds: { lines: coverageThreshold }
39
+ }
40
+ } };
41
+ }
42
+ export { ConfigError, integr8Vitest };
@@ -0,0 +1,28 @@
1
+ # @integr8-media/config Lefthook base.
2
+ # Use it from a repo's lefthook.yml:
3
+ # extends:
4
+ # - node_modules/@integr8-media/config/lefthook/base.yml
5
+
6
+ pre-commit:
7
+ commands:
8
+ biome:
9
+ glob: "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json,jsonc,css}"
10
+ run: npx --no biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
11
+ stage_fixed: true
12
+
13
+ pre-push:
14
+ # piped: run in order and stop at the first failure.
15
+ piped: true
16
+ commands:
17
+ typecheck:
18
+ priority: 1
19
+ run: npm run typecheck
20
+ test-coverage:
21
+ priority: 2
22
+ run: npm run test:coverage
23
+ build:
24
+ priority: 3
25
+ run: npm run build
26
+ playground-smoke:
27
+ priority: 4
28
+ run: npm run playground:smoke --if-present
package/package.json CHANGED
@@ -1,6 +1,116 @@
1
1
  {
2
2
  "name": "@integr8-media/config",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Shared tooling presets for every @integr8-media repo: TypeScript, Biome, Vitest, tsdown, Lefthook and size-limit.",
5
+ "license": "MIT",
6
+ "author": "Mina Louis Leon",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MinaLouisLeon/integr8-config.git"
10
+ },
11
+ "homepage": "https://github.com/MinaLouisLeon/integr8-config#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/MinaLouisLeon/integr8-config/issues"
14
+ },
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "engines": {
18
+ "node": ">=24"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "tsconfig",
23
+ "biome",
24
+ "lefthook"
25
+ ],
26
+ "exports": {
27
+ "./tsconfig/library.json": "./tsconfig/library.json",
28
+ "./tsconfig/react-library.json": "./tsconfig/react-library.json",
29
+ "./tsconfig/node-app.json": "./tsconfig/node-app.json",
30
+ "./tsconfig/react-app.json": "./tsconfig/react-app.json",
31
+ "./biome": "./biome/biome.json",
32
+ "./vitest": {
33
+ "types": "./dist/vitest.d.mts",
34
+ "default": "./dist/vitest.mjs"
35
+ },
36
+ "./tsdown": {
37
+ "types": "./dist/tsdown.d.mts",
38
+ "default": "./dist/tsdown.mjs"
39
+ },
40
+ "./lefthook/base.yml": "./lefthook/base.yml",
41
+ "./size-limit": {
42
+ "types": "./dist/sizeLimit.d.mts",
43
+ "default": "./dist/sizeLimit.mjs"
44
+ },
45
+ "./package.json": "./package.json"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "lint": "biome check",
52
+ "format": "biome format --write",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "vitest run",
55
+ "test:coverage": "vitest run --coverage",
56
+ "build": "tsdown",
57
+ "size": "size-limit",
58
+ "check:exports": "publint && attw --pack --profile esm-only --entrypoints ./vitest ./tsdown ./size-limit",
59
+ "docs": "typedoc",
60
+ "prepare": "lefthook install"
61
+ },
62
+ "peerDependencies": {
63
+ "@arethetypeswrong/cli": ">=0.17.0",
64
+ "@biomejs/biome": ">=2.6.0 <3",
65
+ "@vitest/coverage-v8": ">=4.0.0",
66
+ "lefthook": ">=1.10.0",
67
+ "publint": ">=0.3.0",
68
+ "size-limit": ">=11.0.0",
69
+ "tsdown": ">=0.23.0",
70
+ "typescript": ">=5.7.0",
71
+ "vitest": ">=4.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "@arethetypeswrong/cli": {
75
+ "optional": true
76
+ },
77
+ "@biomejs/biome": {
78
+ "optional": true
79
+ },
80
+ "@vitest/coverage-v8": {
81
+ "optional": true
82
+ },
83
+ "lefthook": {
84
+ "optional": true
85
+ },
86
+ "publint": {
87
+ "optional": true
88
+ },
89
+ "size-limit": {
90
+ "optional": true
91
+ },
92
+ "tsdown": {
93
+ "optional": true
94
+ },
95
+ "typescript": {
96
+ "optional": true
97
+ },
98
+ "vitest": {
99
+ "optional": true
100
+ }
101
+ },
102
+ "devDependencies": {
103
+ "@arethetypeswrong/cli": "^0.18.5",
104
+ "@biomejs/biome": "^2.6.5",
105
+ "@size-limit/file": "^14.3.0",
106
+ "@types/node": "^24.0.0",
107
+ "@vitest/coverage-v8": "^5.1.0",
108
+ "lefthook": "^2.2.2",
109
+ "publint": "^0.3.25",
110
+ "size-limit": "^14.3.0",
111
+ "tsdown": "^0.23.5",
112
+ "typedoc": "^0.28.20",
113
+ "typescript": "~6.0.0",
114
+ "vitest": "^5.1.0"
115
+ }
116
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "@integr8-media/config base",
4
+ "compilerOptions": {
5
+ "target": "ES2024",
6
+ "lib": ["ES2024"],
7
+
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "exactOptionalPropertyTypes": true,
11
+ "noImplicitOverride": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+
14
+ "verbatimModuleSyntax": true,
15
+ "resolveJsonModule": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "skipLibCheck": true
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "@integr8-media/config library (Node or universal)",
4
+ "extends": "./base.json",
5
+ "compilerOptions": {
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext",
8
+ "declaration": true,
9
+ "isolatedDeclarations": true,
10
+ "declarationMap": false
11
+ }
12
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "@integr8-media/config Node application",
4
+ "extends": "./base.json",
5
+ "compilerOptions": {
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext",
8
+ "types": ["node"],
9
+ "declaration": false
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "@integr8-media/config React application",
4
+ "extends": "./base.json",
5
+ "compilerOptions": {
6
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "jsx": "react-jsx",
10
+ "noEmit": true
11
+ }
12
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "@integr8-media/config React library",
4
+ "extends": "./base.json",
5
+ "compilerOptions": {
6
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "jsx": "react-jsx",
10
+ "declaration": true,
11
+ "isolatedDeclarations": true,
12
+ "declarationMap": false
13
+ }
14
+ }