@ncksnydr/config-js 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # @ncksnydr/config-js
2
+
3
+ Shared configuration for TypeScript, ESLint, Prettier and Vitest. Each configuration is a separate entry point, so a Consumer uses only the parts it needs.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add --save-dev @ncksnydr/config-js typescript eslint prettier vitest
9
+ ```
10
+
11
+ ## TypeScript
12
+
13
+ Extend the base in your `tsconfig.json`:
14
+
15
+ ```json
16
+ {
17
+ "extends": "@ncksnydr/config-js/tsconfig"
18
+ }
19
+ ```
20
+
21
+ The base is strict, uses Node's own import rules (`nodenext`), and targets ES2024.
22
+
23
+ The base loads no global types. TypeScript 6 loads none by default, and a browser app must not see Node's types. If your code runs in Node, install `@types/node` and add the Node types yourself:
24
+
25
+ ```json
26
+ {
27
+ "extends": "@ncksnydr/config-js/tsconfig",
28
+ "compilerOptions": {
29
+ "types": ["node"]
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## ESLint
35
+
36
+ Use the configuration as your `eslint.config.js`:
37
+
38
+ ```js
39
+ export { default } from "@ncksnydr/config-js/eslint";
40
+ ```
41
+
42
+ The configuration turns on ESLint's recommended rules and the `strictTypeChecked` rules of typescript-eslint. The typescript-eslint rules read your types, so every TypeScript file must be in a `tsconfig.json`. JavaScript files get no type-aware rules.
43
+
44
+ ### Import boundaries
45
+
46
+ An import boundary stops one part of your code from importing another part. Declare your boundaries with `boundaries()`:
47
+
48
+ ```js
49
+ import config, { boundaries } from "@ncksnydr/config-js/eslint";
50
+ import { defineConfig } from "eslint/config";
51
+
52
+ export default defineConfig(
53
+ config,
54
+ boundaries(import.meta.dirname, [
55
+ {
56
+ because: "A shared package cannot depend on an app.",
57
+ cannotImport: ["apps/*/**"],
58
+ files: "packages/*/**",
59
+ },
60
+ ]),
61
+ );
62
+ ```
63
+
64
+ The first argument is the folder that the paths start from. Pass `import.meta.dirname`, so the paths start at the folder of your `eslint.config.js`. Each boundary has three parts:
65
+
66
+ - `because` is the reason. ESLint prints it in the error.
67
+ - `cannotImport` lists globs of the files that the covered files cannot import.
68
+ - `files` is a glob of the files that the boundary covers.
69
+
70
+ An import across a boundary fails lint:
71
+
72
+ ```text
73
+ packages/shared/heading.ts
74
+ 1:23 error Unexpected path "../../apps/blog/post.js" imported in restricted zone. A shared package cannot depend on an app import-x/no-restricted-paths
75
+ ```
76
+
77
+ A boundary goes one way. In the example, an app can still import a shared package.
78
+
79
+ ## Prettier
80
+
81
+ Use the configuration as your `prettier.config.js`:
82
+
83
+ ```js
84
+ export { default } from "@ncksnydr/config-js/prettier";
85
+ ```
86
+
87
+ The configuration needs Prettier 3.5 or later. It sets every Prettier layout option, so a new Prettier default cannot change your layout. Lines wrap at 120 characters. Strings use double quotes, statements end with a semicolon, and a list gets a comma after its last item. Markdown lines stay as you wrote them.
88
+
89
+ The configuration also sorts imports in three groups: Node's built-in modules, then packages, then relative files. An import in the wrong order fails the check:
90
+
91
+ ```text
92
+ Checking formatting...
93
+ [warn] index.ts
94
+ [warn] Code style issues found in the above file. Run Prettier with --write to fix.
95
+ ```
96
+
97
+ To fix the layout, run `prettier --write .`.
98
+
99
+ ## Vitest
100
+
101
+ Use the preset as your `vitest.config.ts`:
102
+
103
+ ```ts
104
+ export { default } from "@ncksnydr/config-js/vitest";
105
+ ```
106
+
107
+ To change a setting, merge the preset with your own configuration:
108
+
109
+ ```ts
110
+ import preset from "@ncksnydr/config-js/vitest";
111
+ import { defineConfig, mergeConfig } from "vitest/config";
112
+
113
+ export default mergeConfig(preset, defineConfig({ test: { include: ["tests/**/*.test.ts"] } }));
114
+ ```
115
+
116
+ The preset runs tests in Node and finds `*.test.ts` files next to the code. It restores mocks between tests. If it finds no tests, the run fails.
@@ -0,0 +1,9 @@
1
+ import type { Linter } from "eslint";
2
+ export interface Boundary {
3
+ because: string;
4
+ cannotImport: string[];
5
+ files: string;
6
+ }
7
+ export declare function boundaries(root: string, importBoundaries: Boundary[]): Linter.Config;
8
+ declare const _default: import("eslint/config").ConfigObject[];
9
+ export default _default;
package/dist/eslint.js ADDED
@@ -0,0 +1,40 @@
1
+ import js from "@eslint/js";
2
+ import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
3
+ import { importX } from "eslint-plugin-import-x";
4
+ import { defineConfig } from "eslint/config";
5
+ import tseslint from "typescript-eslint";
6
+ export function boundaries(root, importBoundaries) {
7
+ return {
8
+ name: "@ncksnydr/config-js/boundaries",
9
+ plugins: {
10
+ "import-x": importX,
11
+ },
12
+ rules: {
13
+ "import-x/no-restricted-paths": [
14
+ "error",
15
+ {
16
+ basePath: root,
17
+ zones: importBoundaries.map(({ because, cannotImport, files }) => ({
18
+ from: cannotImport,
19
+ message: because,
20
+ target: files,
21
+ })),
22
+ },
23
+ ],
24
+ },
25
+ settings: {
26
+ "import-x/resolver-next": [createTypeScriptImportResolver()],
27
+ },
28
+ };
29
+ }
30
+ export default defineConfig(js.configs.recommended, tseslint.configs.strictTypeChecked, {
31
+ languageOptions: {
32
+ parserOptions: {
33
+ projectService: true,
34
+ },
35
+ },
36
+ }, {
37
+ extends: [tseslint.configs.disableTypeChecked],
38
+ files: ["**/*.{js,mjs,cjs}"],
39
+ });
40
+ //# sourceMappingURL=eslint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint.js","sourceRoot":"","sources":["../src/eslint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AAQzC,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,gBAA4B;IACnE,OAAO;QACL,IAAI,EAAE,gCAAgC;QACtC,OAAO,EAAE;YACP,UAAU,EAAE,OAAO;SACpB;QACD,KAAK,EAAE;YACL,8BAA8B,EAAE;gBAC9B,OAAO;gBACP;oBACE,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;wBACjE,IAAI,EAAE,YAAY;wBAClB,OAAO,EAAE,OAAO;wBAChB,MAAM,EAAE,KAAK;qBACd,CAAC,CAAC;iBACJ;aACF;SACF;QACD,QAAQ,EAAE;YACR,wBAAwB,EAAE,CAAC,8BAA8B,EAAE,CAAC;SAC7D;KACF,CAAC;AACJ,CAAC;AAED,eAAe,YAAY,CACzB,EAAE,CAAC,OAAO,CAAC,WAAW,EACtB,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAClC;IACE,eAAe,EAAE;QACf,aAAa,EAAE;YACb,cAAc,EAAE,IAAI;SACrB;KACF;CACF,EACD;IACE,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,kBAAkB,CAAC;IAC9C,KAAK,EAAE,CAAC,mBAAmB,CAAC;CAC7B,CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PrettierConfig } from "@ianvs/prettier-plugin-sort-imports";
2
+ declare const config: PrettierConfig;
3
+ export default config;
@@ -0,0 +1,28 @@
1
+ import { fileURLToPath } from "node:url";
2
+ const sortImportsPlugin = fileURLToPath(import.meta.resolve("@ianvs/prettier-plugin-sort-imports"));
3
+ const config = {
4
+ arrowParens: "always",
5
+ bracketSameLine: false,
6
+ bracketSpacing: true,
7
+ embeddedLanguageFormatting: "auto",
8
+ endOfLine: "lf",
9
+ experimentalOperatorPosition: "end",
10
+ experimentalTernaries: false,
11
+ htmlWhitespaceSensitivity: "css",
12
+ importOrder: ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^[.]"],
13
+ jsxSingleQuote: false,
14
+ objectWrap: "preserve",
15
+ plugins: [sortImportsPlugin],
16
+ printWidth: 120,
17
+ proseWrap: "preserve",
18
+ quoteProps: "as-needed",
19
+ semi: true,
20
+ singleAttributePerLine: false,
21
+ singleQuote: false,
22
+ tabWidth: 2,
23
+ trailingComma: "all",
24
+ useTabs: false,
25
+ vueIndentScriptAndStyle: false,
26
+ };
27
+ export default config;
28
+ //# sourceMappingURL=prettier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prettier.js","sourceRoot":"","sources":["../src/prettier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,iBAAiB,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,CAAC;AAEpG,MAAM,MAAM,GAAmB;IAC7B,WAAW,EAAE,QAAQ;IACrB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,IAAI;IACpB,0BAA0B,EAAE,MAAM;IAClC,SAAS,EAAE,IAAI;IACf,4BAA4B,EAAE,KAAK;IACnC,qBAAqB,EAAE,KAAK;IAC5B,yBAAyB,EAAE,KAAK;IAChC,WAAW,EAAE,CAAC,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,CAAC;IACnE,cAAc,EAAE,KAAK;IACrB,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,CAAC,iBAAiB,CAAC;IAC5B,UAAU,EAAE,GAAG;IACf,SAAS,EAAE,UAAU;IACrB,UAAU,EAAE,WAAW;IACvB,IAAI,EAAE,IAAI;IACV,sBAAsB,EAAE,KAAK;IAC7B,WAAW,EAAE,KAAK;IAClB,QAAQ,EAAE,CAAC;IACX,aAAa,EAAE,KAAK;IACpB,OAAO,EAAE,KAAK;IACd,uBAAuB,EAAE,KAAK;CAC/B,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
package/dist/vitest.js ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "vitest/config";
2
+ export default defineConfig({
3
+ test: {
4
+ environment: "node",
5
+ include: ["**/*.test.ts"],
6
+ restoreMocks: true,
7
+ passWithNoTests: false,
8
+ },
9
+ });
10
+ //# sourceMappingURL=vitest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM;QACnB,OAAO,EAAE,CAAC,cAAc,CAAC;QACzB,YAAY,EAAE,IAAI;QAClB,eAAe,EAAE,KAAK;KACvB;CACF,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@ncksnydr/config-js",
3
+ "version": "0.2.0",
4
+ "description": "Shared TypeScript, ESLint, Prettier and Vitest configuration for Nick Snyder's codebases",
5
+ "repository": {
6
+ "directory": "packages/config-js",
7
+ "type": "git",
8
+ "url": "git+https://github.com/ncksnydr/e2.git"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ "./eslint": {
13
+ "types": "./dist/eslint.d.ts",
14
+ "default": "./dist/eslint.js"
15
+ },
16
+ "./prettier": {
17
+ "types": "./dist/prettier.d.ts",
18
+ "default": "./dist/prettier.js"
19
+ },
20
+ "./tsconfig": "./tsconfig/base.json",
21
+ "./vitest": {
22
+ "types": "./dist/vitest.d.ts",
23
+ "default": "./dist/vitest.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "!src/**/*.test.ts",
30
+ "!src/fixtures",
31
+ "!src/testing",
32
+ "tsconfig"
33
+ ],
34
+ "peerDependencies": {
35
+ "eslint": "^10.0.0",
36
+ "prettier": "^3.5.0",
37
+ "typescript": "^6.0.0",
38
+ "vitest": "^5.0.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "eslint": {
42
+ "optional": true
43
+ },
44
+ "prettier": {
45
+ "optional": true
46
+ },
47
+ "typescript": {
48
+ "optional": true
49
+ },
50
+ "vitest": {
51
+ "optional": true
52
+ }
53
+ },
54
+ "dependencies": {
55
+ "@eslint/js": "^10.0.1",
56
+ "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
57
+ "eslint-import-resolver-typescript": "^4.4.5",
58
+ "eslint-plugin-import-x": "^4.18.1",
59
+ "typescript-eslint": "^8.72.2"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^26.6.6",
63
+ "eslint": "^10.14.0",
64
+ "prettier": "^3.10.0",
65
+ "typescript": "~6.0.3",
66
+ "vitest": "^5.1.0"
67
+ },
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "scripts": {
72
+ "build": "tsc -p tsconfig.build.json",
73
+ "lint": "eslint .",
74
+ "test": "vitest run",
75
+ "typecheck": "tsc --noEmit"
76
+ }
77
+ }
package/src/eslint.ts ADDED
@@ -0,0 +1,53 @@
1
+ import js from "@eslint/js";
2
+ import type { Linter } from "eslint";
3
+ import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
4
+ import { importX } from "eslint-plugin-import-x";
5
+ import { defineConfig } from "eslint/config";
6
+ import tseslint from "typescript-eslint";
7
+
8
+ export interface Boundary {
9
+ because: string;
10
+ cannotImport: string[];
11
+ files: string;
12
+ }
13
+
14
+ export function boundaries(root: string, importBoundaries: Boundary[]): Linter.Config {
15
+ return {
16
+ name: "@ncksnydr/config-js/boundaries",
17
+ plugins: {
18
+ "import-x": importX,
19
+ },
20
+ rules: {
21
+ "import-x/no-restricted-paths": [
22
+ "error",
23
+ {
24
+ basePath: root,
25
+ zones: importBoundaries.map(({ because, cannotImport, files }) => ({
26
+ from: cannotImport,
27
+ message: because,
28
+ target: files,
29
+ })),
30
+ },
31
+ ],
32
+ },
33
+ settings: {
34
+ "import-x/resolver-next": [createTypeScriptImportResolver()],
35
+ },
36
+ };
37
+ }
38
+
39
+ export default defineConfig(
40
+ js.configs.recommended,
41
+ tseslint.configs.strictTypeChecked,
42
+ {
43
+ languageOptions: {
44
+ parserOptions: {
45
+ projectService: true,
46
+ },
47
+ },
48
+ },
49
+ {
50
+ extends: [tseslint.configs.disableTypeChecked],
51
+ files: ["**/*.{js,mjs,cjs}"],
52
+ },
53
+ );
@@ -0,0 +1,31 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import type { PrettierConfig } from "@ianvs/prettier-plugin-sort-imports";
3
+
4
+ const sortImportsPlugin = fileURLToPath(import.meta.resolve("@ianvs/prettier-plugin-sort-imports"));
5
+
6
+ const config: PrettierConfig = {
7
+ arrowParens: "always",
8
+ bracketSameLine: false,
9
+ bracketSpacing: true,
10
+ embeddedLanguageFormatting: "auto",
11
+ endOfLine: "lf",
12
+ experimentalOperatorPosition: "end",
13
+ experimentalTernaries: false,
14
+ htmlWhitespaceSensitivity: "css",
15
+ importOrder: ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^[.]"],
16
+ jsxSingleQuote: false,
17
+ objectWrap: "preserve",
18
+ plugins: [sortImportsPlugin],
19
+ printWidth: 120,
20
+ proseWrap: "preserve",
21
+ quoteProps: "as-needed",
22
+ semi: true,
23
+ singleAttributePerLine: false,
24
+ singleQuote: false,
25
+ tabWidth: 2,
26
+ trailingComma: "all",
27
+ useTabs: false,
28
+ vueIndentScriptAndStyle: false,
29
+ };
30
+
31
+ export default config;
package/src/vitest.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ include: ["**/*.test.ts"],
7
+ restoreMocks: true,
8
+ passWithNoTests: false,
9
+ },
10
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "noUncheckedIndexedAccess": true,
6
+ "exactOptionalPropertyTypes": true,
7
+ "noImplicitOverride": true,
8
+ "noFallthroughCasesInSwitch": true,
9
+ "verbatimModuleSyntax": true,
10
+ "erasableSyntaxOnly": true,
11
+ "module": "nodenext",
12
+ "moduleResolution": "nodenext",
13
+ "target": "es2024",
14
+ "skipLibCheck": true,
15
+ "declaration": true,
16
+ "sourceMap": true
17
+ }
18
+ }