@ankhorage/devtools 1.2.0 → 1.3.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 CHANGED
@@ -4,7 +4,7 @@ Shared development tools and repository standards for Ankhorage TypeScript proje
4
4
 
5
5
  ## What it owns
6
6
 
7
- `@ankhorage/devtools` is the single source of truth for these equal, separate concerns:
7
+ `@ankhorage/devtools` is the single source of truth for these separate concerns:
8
8
 
9
9
  ```text
10
10
  src/
@@ -13,25 +13,39 @@ src/
13
13
  ├── eslint/
14
14
  ├── prettier/
15
15
  ├── knip/
16
+ ├── package/
16
17
  ├── workflows/
17
18
  └── vscode/
18
19
  ```
19
20
 
20
- - `eslint`: shared flat ESLint configuration and the bundled ESLint runner
21
+ - `eslint`: shared flat ESLint configuration, automatic project profiles, and the bundled ESLint runner
21
22
  - `prettier`: shared Prettier configuration and the bundled Prettier runner
22
- - `knip`: shared Knip configuration and the bundled Knip runner
23
+ - `knip`: shared Knip configuration helpers and the bundled Knip runner
24
+ - `package`: merge-aware synchronization of the shared `package.json` tooling contract
23
25
  - `workflows`: canonical `.github/workflows/ci.yml` and `release.yml`
24
26
  - `vscode`: canonical `.vscode/settings.json` and `extensions.json`
25
27
 
26
- There is no generic template layer around these concerns. Each tool owns its canonical files and behavior.
28
+ The package owns the supported ESLint, TypeScript ESLint, Prettier, Knip, security, React, React Hooks, React Native, import/sort, unused-import, and formatting-plugin versions used by consuming repositories.
27
29
 
28
- ## Installation
30
+ ## Bootstrap
31
+
32
+ For a repository that does not yet depend on the shared toolchain:
29
33
 
30
34
  ```bash
31
35
  bun add -D @ankhorage/devtools
36
+ bunx @ankhorage/ankh devtools sync .
37
+ bun install
38
+ ```
39
+
40
+ After the first install, the normal workflow is:
41
+
42
+ ```bash
43
+ ankh devtools sync
32
44
  ```
33
45
 
34
- The package owns the ESLint, Prettier, and Knip versions used by consuming repositories. Do not install those tools directly unless a repository intentionally opts out of the shared Ankhorage toolchain.
46
+ The target path is optional and defaults to the current working directory.
47
+
48
+ Synchronization ensures `@ankhorage/devtools` is declared using the version of the provider performing the sync, installs the standard package scripts, and removes direct devDependencies for tools/plugins owned by devtools. Unrelated package metadata, dependencies, and scripts are preserved.
35
49
 
36
50
  ## Ankh provider
37
51
 
@@ -42,6 +56,14 @@ The package is discovered under the `devtools` category and exposes these capabi
42
56
  - `devtools.knip`
43
57
  - `devtools.sync`
44
58
  - `devtools.status`
59
+ - `devtools.eslint.sync`
60
+ - `devtools.eslint.status`
61
+ - `devtools.prettier.sync`
62
+ - `devtools.prettier.status`
63
+ - `devtools.knip.sync`
64
+ - `devtools.knip.status`
65
+ - `devtools.package.sync`
66
+ - `devtools.package.status`
45
67
  - `devtools.workflows.sync`
46
68
  - `devtools.workflows.status`
47
69
  - `devtools.vscode.sync`
@@ -61,13 +83,13 @@ ankh devtools format -- --check .
61
83
  ankh devtools knip -- --production
62
84
  ```
63
85
 
64
- These commands delegate to the same bundled tools as the package binaries:
86
+ These delegate to the same bundled tools as the package binaries:
65
87
 
66
88
  - `ankh devtools lint` → `ankhorage-eslint`
67
89
  - `ankh devtools format` → `ankhorage-prettier`
68
90
  - `ankh devtools knip` → `ankhorage-knip`
69
91
 
70
- Recommended package scripts:
92
+ The synchronized package scripts are:
71
93
 
72
94
  ```json
73
95
  {
@@ -83,159 +105,184 @@ Recommended package scripts:
83
105
 
84
106
  ## Repository synchronization
85
107
 
86
- ### Synchronize all managed files
108
+ Synchronize or inspect every managed concern:
87
109
 
88
110
  ```bash
89
111
  ankh devtools sync .
90
- ```
91
-
92
- The target path is optional and defaults to the current working directory:
93
-
94
- ```bash
95
- ankh devtools sync
96
- ```
97
-
98
- ### Report drift without changing files
99
-
100
- ```bash
101
112
  ankh devtools status .
102
113
  ```
103
114
 
104
- `status` exits with code `1` when any managed file is missing or outdated. It exits with code `0` when all managed files are current.
105
-
106
- Example output:
107
-
108
- ```text
109
- ✓ .github/workflows/ci.yml
110
- ✗ .github/workflows/release.yml outdated
111
- + .vscode/settings.json missing
112
- ✓ .vscode/extensions.json
113
- ```
114
-
115
- ### Synchronize one concern
115
+ Synchronize one concern:
116
116
 
117
117
  ```bash
118
+ ankh devtools eslint sync .
119
+ ankh devtools prettier sync .
120
+ ankh devtools knip sync .
121
+ ankh devtools package sync .
118
122
  ankh devtools workflows sync .
119
123
  ankh devtools vscode sync .
120
124
  ```
121
125
 
122
- ### Report one concern
126
+ Report one concern:
123
127
 
124
128
  ```bash
129
+ ankh devtools eslint status .
130
+ ankh devtools prettier status .
131
+ ankh devtools knip status .
132
+ ankh devtools package status .
125
133
  ankh devtools workflows status .
126
134
  ankh devtools vscode status .
127
135
  ```
128
136
 
129
- ### Preview synchronization
137
+ Preview synchronization without writing:
130
138
 
131
139
  ```bash
132
140
  ankh devtools sync . --dry-run
133
- ankh devtools workflows sync . --dry-run
134
- ankh devtools vscode sync . --dry-run
141
+ ankh devtools eslint sync . --dry-run
142
+ ankh devtools package sync . --dry-run
135
143
  ```
136
144
 
137
- A dry run reports `would create` and `would update` actions without writing any files.
145
+ A dry run reports `would create` and `would update` actions without mutating files. `status` exits with code `1` when managed state has drifted and `0` when it is current.
138
146
 
139
147
  ## Synchronization guarantees
140
148
 
141
149
  Synchronization is deterministic and idempotent:
142
150
 
143
- - missing managed files are created
144
- - outdated managed files are replaced with the canonical package version
145
- - current managed files are left untouched
146
- - unrelated files are never modified
147
- - unknown files in `.github/workflows` and `.vscode` are never deleted
148
- - repeating `sync` after a successful run produces only `unchanged` results
151
+ - missing managed artifacts are created
152
+ - outdated centrally owned artifacts are updated
153
+ - current artifacts are left untouched
154
+ - unrelated files and package fields are preserved
155
+ - repeated sync produces only `unchanged` results
149
156
  - invalid target paths and write failures return a non-zero exit code
157
+ - create-only repository extension files are never overwritten after creation
150
158
 
151
- The canonical files are packaged with `@ankhorage/devtools`; synchronization does not fetch mutable files from GitHub at runtime.
152
-
153
- ## Managed GitHub Actions workflows
154
-
155
- `workflows` owns exactly:
156
-
157
- ```text
158
- .github/workflows/ci.yml
159
- .github/workflows/release.yml
160
- ```
159
+ The canonical workflow and VS Code files are packaged with `@ankhorage/devtools`; synchronization does not fetch mutable files from GitHub at runtime.
161
160
 
162
- The CI workflow:
161
+ ## ESLint profiles
163
162
 
164
- - checks out full history
165
- - installs the pinned Bun version
166
- - installs dependencies with `bun install --frozen-lockfile`
167
- - runs `bunx @ankhorage/ankh doctor validate .`
168
- - conditionally runs build, lint, format check, Knip, tests, and typecheck when scripts exist
169
- - conditionally runs `changeset:status` for pull requests when the script exists
163
+ `createConfig()` defaults to `profile: 'auto'`.
170
164
 
171
- The release workflow:
165
+ Automatic detection reads the consuming repository's `package.json` and delegates project trait detection to `@ankhorage/utility/project`. Dependency signals are considered across `dependencies`, `devDependencies`, and `peerDependencies`.
172
166
 
173
- - checks out full history
174
- - configures Bun and Node for npm publishing
175
- - installs dependencies with the frozen lockfile
176
- - conditionally builds
177
- - runs the Changesets release PR/publish flow
178
- - protects release execution with a concurrency group
179
- - skips cleanly when no Changesets configuration exists
180
-
181
- ## Managed VS Code configuration
182
-
183
- `vscode` owns exactly:
167
+ Profile precedence is:
184
168
 
185
169
  ```text
186
- .vscode/settings.json
187
- .vscode/extensions.json
170
+ React Native / Expo
171
+
172
+ react-native
173
+ ↓ includes
174
+ react
175
+ ↓ includes
176
+ base
188
177
  ```
189
178
 
190
- The shared settings use the workspace TypeScript SDK, enable the workspace-SDK prompt, configure intentional ESLint save actions, and enforce basic whitespace/newline consistency.
191
-
192
- The extension recommendations are limited to the standard Ankhorage workflow:
193
-
194
- - Bun
195
- - ESLint
196
- - Prettier
197
- - YAML
198
- - GitHub Actions
199
-
200
- `launch.json` is intentionally not managed globally. Libraries, CLIs, Expo packages, services, and integration repositories require different debug configurations.
179
+ A React or Next.js project selects `react`. A React Native or Expo project selects `react-native`. Everything else selects `base`.
201
180
 
202
- ## ESLint
203
-
204
- Create `eslint.config.mjs`:
181
+ An unusual repository can opt out of automatic selection:
205
182
 
206
183
  ```js
207
184
  import { createConfig } from '@ankhorage/devtools/eslint';
208
185
 
209
186
  export default createConfig({
210
- files: ['src/**/*.{ts,tsx}'],
187
+ files: ['src/**/*.ts'],
188
+ profile: 'base',
211
189
  project: ['./tsconfig.json'],
212
190
  tsconfigRootDir: import.meta.dirname,
213
191
  });
214
192
  ```
215
193
 
216
- ## Prettier
194
+ The base profile enforces the shared TypeScript policy plus:
195
+
196
+ - maximum 50 effective lines per function
197
+ - maximum 300 effective lines per file
198
+ - modified cyclomatic complexity maximum 15
199
+ - security review for dynamic object access
200
+ - rejection of non-literal `require()` calls
201
+
202
+ The React profile adds React and React Hooks correctness rules. The React Native profile composes the React profile and adds the selected React Native style rules.
203
+
204
+ ### Managed ESLint setup and local overrides
217
205
 
218
- ES modules:
206
+ `ankh devtools eslint sync` centrally owns `eslint.config.mjs` and creates `eslint.local.config.mjs` once.
207
+
208
+ The canonical wrapper uses automatic profile detection and appends repository-owned flat-config entries:
219
209
 
220
210
  ```js
221
- export { default } from '@ankhorage/devtools/prettier';
211
+ import { createConfig } from '@ankhorage/devtools/eslint';
212
+ import localConfig from './eslint.local.config.mjs';
213
+
214
+ const localEntries = Array.isArray(localConfig) ? localConfig : [localConfig];
215
+
216
+ export default [
217
+ ...createConfig({
218
+ files: ['src/**/*.{ts,tsx}'],
219
+ project: ['./tsconfig.json'],
220
+ tsconfigRootDir: import.meta.dirname,
221
+ }),
222
+ ...localEntries,
223
+ ];
222
224
  ```
223
225
 
224
- CommonJS:
226
+ Use `eslint.local.config.mjs` for narrow repository-specific flat-config overrides, including temporary file-specific migration overrides. On first synchronization, an existing non-canonical `eslint.config.mjs` is preserved as the initial local config before the canonical wrapper is installed. Synchronization never overwrites that local file afterward.
225
227
 
226
- ```js
227
- module.exports = require('@ankhorage/devtools/prettier');
228
+ ## Prettier
229
+
230
+ `ankh devtools prettier sync` owns `.prettierrc.js` and emits the correct ESM or CommonJS delegate based on the repository's `package.json` module type.
231
+
232
+ The consumer delegates formatting policy to:
233
+
234
+ ```text
235
+ @ankhorage/devtools/prettier
228
236
  ```
229
237
 
230
238
  ## Knip
231
239
 
240
+ `ankh devtools knip sync` bootstraps `knip.config.ts` with:
241
+
232
242
  ```ts
233
243
  import { createKnipConfig } from '@ankhorage/devtools/knip';
234
244
 
235
245
  export default createKnipConfig();
236
246
  ```
237
247
 
238
- Monorepos can use `createKnipMonorepoConfig` and add narrow repository-specific entries, projects, ignores, binaries, dependencies, or workspace overrides.
248
+ `knip.config.ts` is create-only after bootstrap so repositories can retain narrow local entries, projects, ignores, binaries, dependencies, or switch to `createKnipMonorepoConfig()` without synchronization overwriting those extensions.
249
+
250
+ ## Managed package contract
251
+
252
+ `ankh devtools package sync` merge-updates `package.json` rather than replacing it.
253
+
254
+ It owns:
255
+
256
+ - the `@ankhorage/devtools` devDependency version range
257
+ - `lint`
258
+ - `lint:fix`
259
+ - `format`
260
+ - `format:check`
261
+ - `knip`
262
+
263
+ It also removes direct devDependencies for tools and ESLint plugins already provided by `@ankhorage/devtools`. Unrelated scripts, dependencies, metadata, and repository-specific configuration remain unchanged.
264
+
265
+ ## Managed GitHub Actions workflows
266
+
267
+ `workflows` owns exactly:
268
+
269
+ ```text
270
+ .github/workflows/ci.yml
271
+ .github/workflows/release.yml
272
+ ```
273
+
274
+ The CI workflow installs the pinned Bun version with the frozen lockfile, builds before repository-provider validation, runs `bunx @ankhorage/ankh doctor validate .`, and conditionally runs lint, formatting, Knip, tests, typecheck, and Changesets checks.
275
+
276
+ ## Managed VS Code configuration
277
+
278
+ `vscode` owns exactly:
279
+
280
+ ```text
281
+ .vscode/settings.json
282
+ .vscode/extensions.json
283
+ ```
284
+
285
+ Unknown workflow and VS Code files are never deleted.
239
286
 
240
287
  ## Adding another managed concern
241
288
 
@@ -243,7 +290,7 @@ A new concern should:
243
290
 
244
291
  1. live in its own sibling directory under `src/tools`
245
292
  2. define only the files and behavior it owns
246
- 3. expose deterministic status and synchronization through the shared managed-file engine
293
+ 3. expose deterministic status and synchronization
247
294
  4. add provider commands under `ankh devtools`
248
- 5. include source-tree, built-package, dry-run, status, and idempotence coverage
249
- 6. document overwrite and exit-code behavior
295
+ 5. include dry-run, status, and idempotence coverage
296
+ 6. document its central ownership and repository-owned extension points
@@ -1,7 +1,7 @@
1
1
  export type DevtoolsToolName = 'format' | 'knip' | 'lint';
2
- type DevtoolsManagedScope = 'all' | 'vscode' | 'workflows';
2
+ type DevtoolsManagedScope = 'all' | 'eslint' | 'knip' | 'package' | 'prettier' | 'vscode' | 'workflows';
3
3
  type DevtoolsManagedOperation = 'status' | 'sync';
4
- type DevtoolsCapability = 'devtools.format' | 'devtools.knip' | 'devtools.lint' | 'devtools.status' | 'devtools.sync' | 'devtools.vscode.status' | 'devtools.vscode.sync' | 'devtools.workflows.status' | 'devtools.workflows.sync';
4
+ type DevtoolsCapability = 'devtools.format' | 'devtools.knip' | 'devtools.lint' | 'devtools.status' | 'devtools.sync' | 'devtools.eslint.status' | 'devtools.eslint.sync' | 'devtools.knip.status' | 'devtools.knip.sync' | 'devtools.package.status' | 'devtools.package.sync' | 'devtools.prettier.status' | 'devtools.prettier.sync' | 'devtools.vscode.status' | 'devtools.vscode.sync' | 'devtools.workflows.status' | 'devtools.workflows.sync';
5
5
  interface DevtoolsCommandBase {
6
6
  readonly path: readonly [string, ...string[]];
7
7
  readonly capability: DevtoolsCapability;
@@ -1,79 +1,21 @@
1
1
  const DEVTOOLS_COMMANDS = [
2
- {
3
- kind: 'external',
4
- toolName: 'lint',
5
- path: ['lint'],
6
- capability: 'devtools.lint',
7
- summary: 'Run the shared ESLint toolchain.',
8
- packageName: 'eslint',
9
- binName: 'eslint',
10
- },
11
- {
12
- kind: 'external',
13
- toolName: 'format',
14
- path: ['format'],
15
- capability: 'devtools.format',
16
- summary: 'Run the shared Prettier toolchain.',
17
- packageName: 'prettier',
18
- binName: 'prettier',
19
- },
20
- {
21
- kind: 'external',
22
- toolName: 'knip',
23
- path: ['knip'],
24
- capability: 'devtools.knip',
25
- summary: 'Run the shared Knip toolchain.',
26
- packageName: 'knip',
27
- binName: 'knip',
28
- },
29
- {
30
- kind: 'repository',
31
- path: ['sync'],
32
- capability: 'devtools.sync',
33
- summary: 'Synchronize all centrally managed repository files.',
34
- scope: 'all',
35
- operation: 'sync',
36
- },
37
- {
38
- kind: 'repository',
39
- path: ['status'],
40
- capability: 'devtools.status',
41
- summary: 'Report drift for all centrally managed repository files.',
42
- scope: 'all',
43
- operation: 'status',
44
- },
45
- {
46
- kind: 'repository',
47
- path: ['workflows', 'sync'],
48
- capability: 'devtools.workflows.sync',
49
- summary: 'Synchronize the canonical GitHub Actions workflows.',
50
- scope: 'workflows',
51
- operation: 'sync',
52
- },
53
- {
54
- kind: 'repository',
55
- path: ['workflows', 'status'],
56
- capability: 'devtools.workflows.status',
57
- summary: 'Report drift for the canonical GitHub Actions workflows.',
58
- scope: 'workflows',
59
- operation: 'status',
60
- },
61
- {
62
- kind: 'repository',
63
- path: ['vscode', 'sync'],
64
- capability: 'devtools.vscode.sync',
65
- summary: 'Synchronize the canonical VS Code workspace configuration.',
66
- scope: 'vscode',
67
- operation: 'sync',
68
- },
69
- {
70
- kind: 'repository',
71
- path: ['vscode', 'status'],
72
- capability: 'devtools.vscode.status',
73
- summary: 'Report drift for the canonical VS Code workspace configuration.',
74
- scope: 'vscode',
75
- operation: 'status',
76
- },
2
+ externalCommand('lint', 'devtools.lint', 'Run the shared ESLint toolchain.', 'eslint'),
3
+ externalCommand('format', 'devtools.format', 'Run the shared Prettier toolchain.', 'prettier'),
4
+ externalCommand('knip', 'devtools.knip', 'Run the shared Knip toolchain.', 'knip'),
5
+ repositoryCommand(['sync'], 'devtools.sync', 'Synchronize all managed repository setup.', 'all', 'sync'),
6
+ repositoryCommand(['status'], 'devtools.status', 'Report drift for all managed repository setup.', 'all', 'status'),
7
+ repositoryCommand(['eslint', 'sync'], 'devtools.eslint.sync', 'Synchronize the shared ESLint setup.', 'eslint', 'sync'),
8
+ repositoryCommand(['eslint', 'status'], 'devtools.eslint.status', 'Report drift for the shared ESLint setup.', 'eslint', 'status'),
9
+ repositoryCommand(['prettier', 'sync'], 'devtools.prettier.sync', 'Synchronize the shared Prettier setup.', 'prettier', 'sync'),
10
+ repositoryCommand(['prettier', 'status'], 'devtools.prettier.status', 'Report drift for the shared Prettier setup.', 'prettier', 'status'),
11
+ repositoryCommand(['knip', 'sync'], 'devtools.knip.sync', 'Synchronize the shared Knip setup.', 'knip', 'sync'),
12
+ repositoryCommand(['knip', 'status'], 'devtools.knip.status', 'Report drift for the shared Knip setup.', 'knip', 'status'),
13
+ repositoryCommand(['package', 'sync'], 'devtools.package.sync', 'Synchronize the shared package.json contract.', 'package', 'sync'),
14
+ repositoryCommand(['package', 'status'], 'devtools.package.status', 'Report drift for the shared package.json contract.', 'package', 'status'),
15
+ repositoryCommand(['workflows', 'sync'], 'devtools.workflows.sync', 'Synchronize the canonical GitHub Actions workflows.', 'workflows', 'sync'),
16
+ repositoryCommand(['workflows', 'status'], 'devtools.workflows.status', 'Report drift for the canonical GitHub Actions workflows.', 'workflows', 'status'),
17
+ repositoryCommand(['vscode', 'sync'], 'devtools.vscode.sync', 'Synchronize the canonical VS Code workspace configuration.', 'vscode', 'sync'),
18
+ repositoryCommand(['vscode', 'status'], 'devtools.vscode.status', 'Report drift for the canonical VS Code workspace configuration.', 'vscode', 'status'),
77
19
  ];
78
20
  export function getDevtoolsCommands() {
79
21
  return DEVTOOLS_COMMANDS;
@@ -90,3 +32,17 @@ export function getDevtoolsToolCommand(toolName) {
90
32
  }
91
33
  throw new Error(`Unknown devtools tool command: ${toolName}`);
92
34
  }
35
+ function externalCommand(toolName, capability, summary, packageName) {
36
+ return {
37
+ kind: 'external',
38
+ toolName,
39
+ path: [toolName],
40
+ capability,
41
+ summary,
42
+ packageName,
43
+ binName: packageName,
44
+ };
45
+ }
46
+ function repositoryCommand(path, capability, summary, scope, operation) {
47
+ return { kind: 'repository', path, capability, summary, scope, operation };
48
+ }
@@ -2,10 +2,10 @@ declare const provider: {
2
2
  id: string;
3
3
  category: string;
4
4
  version: string;
5
- capabilities: ("devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync")[];
5
+ capabilities: ("devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.eslint.status" | "devtools.eslint.sync" | "devtools.knip.status" | "devtools.knip.sync" | "devtools.package.status" | "devtools.package.sync" | "devtools.prettier.status" | "devtools.prettier.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync")[];
6
6
  commands: {
7
7
  path: [string, ...string[]];
8
- capability: "devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync";
8
+ capability: "devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.eslint.status" | "devtools.eslint.sync" | "devtools.knip.status" | "devtools.knip.sync" | "devtools.package.status" | "devtools.package.sync" | "devtools.prettier.status" | "devtools.prettier.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync";
9
9
  summary: string;
10
10
  }[];
11
11
  handlers: {
package/dist/cli/index.js CHANGED
@@ -1,3 +1,22 @@
1
+ /***
2
+ * Run and synchronize the shared development toolchain through the Ankh CLI.
3
+ *
4
+ * `ankh devtools lint`, `ankh devtools format`, and `ankh devtools knip` execute the bundled
5
+ * ESLint, Prettier, and Knip versions. Repository synchronization is available through
6
+ * `ankh devtools sync` and `ankh devtools status`, with focused `eslint`, `prettier`, `knip`,
7
+ * `package`, `workflows`, and `vscode` sync/status subcommands.
8
+ *
9
+ * Sync commands accept an optional target directory and `--dry-run`. Aggregate sync is
10
+ * deterministic and idempotent: canonical managed files are created or updated, create-only
11
+ * local extension files remain repository-owned, and package metadata is merge-updated without
12
+ * replacing unrelated fields.
13
+ *
14
+ * Fresh repositories can bootstrap the standard setup with `ankh devtools sync .` after adding
15
+ * `@ankhorage/devtools`. Existing ESLint configuration is preserved during first migration as a
16
+ * local extension before the canonical auto-detecting wrapper is installed.
17
+ *
18
+ * @readme
19
+ */
1
20
  import { readFileSync } from 'node:fs';
2
21
  import { getDevtoolsCommands } from './commands.js';
3
22
  import { runProviderCommand } from './runProviderCommand.js';
@@ -1,30 +1,18 @@
1
+ import { eslintManagedFiles } from '../tools/eslint/managed.js';
2
+ import { knipManagedFiles } from '../tools/knip/managed.js';
3
+ import { inspectPackageManifest, readCurrentDevtoolsVersion, syncPackageManifest, } from '../tools/package/index.js';
4
+ import { prettierManagedFiles } from '../tools/prettier/managed.js';
1
5
  import { inspectManagedFiles, resolveManagedTargetDirectory, syncManagedFiles, } from '../tools/shared/managedFiles.js';
2
6
  import { vscodeManagedFiles } from '../tools/vscode/index.js';
3
7
  import { workflowManagedFiles } from '../tools/workflows/index.js';
4
8
  export async function runRepositoryCommand(command, argv, context) {
5
- let parsedArguments;
6
9
  try {
7
- parsedArguments = parseRepositoryArguments(argv, command.operation === 'sync');
8
- }
9
- catch (error) {
10
- context.writeStderr(`${getErrorMessage(error)}\n`);
11
- return { exitCode: 1 };
12
- }
13
- try {
14
- const targetDirectory = await resolveManagedTargetDirectory(context.cwd, parsedArguments.targetPath);
15
- const definitions = getManagedFiles(command.scope);
16
- if (command.operation === 'status') {
17
- const statuses = await inspectManagedFiles(targetDirectory, definitions);
18
- writeStatusOutput(statuses, context);
19
- return {
20
- exitCode: statuses.some((status) => status.state !== 'current') ? 1 : 0,
21
- };
22
- }
23
- const results = await syncManagedFiles(targetDirectory, definitions, {
24
- dryRun: parsedArguments.dryRun,
25
- });
26
- writeSyncOutput(results, context);
27
- return { exitCode: 0 };
10
+ const parsed = parseRepositoryArguments(argv, command.operation === 'sync');
11
+ const targetDirectory = await resolveManagedTargetDirectory(context.cwd, parsed.targetPath);
12
+ const devtoolsVersion = readCurrentDevtoolsVersion();
13
+ return command.operation === 'status'
14
+ ? await runStatus(command.scope, targetDirectory, devtoolsVersion, context)
15
+ : await runSync(command.scope, targetDirectory, devtoolsVersion, parsed.dryRun, context);
28
16
  }
29
17
  catch (error) {
30
18
  context.writeStderr(`${getErrorMessage(error)}\n`);
@@ -52,14 +40,39 @@ export function parseRepositoryArguments(argv, allowDryRun) {
52
40
  }
53
41
  return { dryRun, targetPath };
54
42
  }
43
+ async function runStatus(scope, targetDirectory, devtoolsVersion, context) {
44
+ const statuses = [];
45
+ if (scope === 'all' || scope === 'package') {
46
+ statuses.push(await inspectPackageManifest(targetDirectory, devtoolsVersion));
47
+ }
48
+ statuses.push(...(await inspectManagedFiles(targetDirectory, getManagedFiles(scope))));
49
+ writeStatusOutput(statuses, context);
50
+ return { exitCode: statuses.some((status) => status.state !== 'current') ? 1 : 0 };
51
+ }
52
+ async function runSync(scope, targetDirectory, devtoolsVersion, dryRun, context) {
53
+ const results = [];
54
+ if (scope === 'all' || scope === 'package') {
55
+ results.push(await syncPackageManifest(targetDirectory, devtoolsVersion, { dryRun }));
56
+ }
57
+ results.push(...(await syncManagedFiles(targetDirectory, getManagedFiles(scope), { dryRun })));
58
+ writeSyncOutput(results, context);
59
+ return { exitCode: 0 };
60
+ }
55
61
  function getManagedFiles(scope) {
56
- if (scope === 'workflows') {
57
- return workflowManagedFiles;
62
+ const definitionsByScope = {
63
+ eslint: eslintManagedFiles,
64
+ knip: knipManagedFiles,
65
+ prettier: prettierManagedFiles,
66
+ vscode: vscodeManagedFiles,
67
+ workflows: workflowManagedFiles,
68
+ };
69
+ if (scope === 'all') {
70
+ return Object.values(definitionsByScope).flat();
58
71
  }
59
- if (scope === 'vscode') {
60
- return vscodeManagedFiles;
72
+ if (scope === 'package') {
73
+ return [];
61
74
  }
62
- return [...workflowManagedFiles, ...vscodeManagedFiles];
75
+ return definitionsByScope[scope];
63
76
  }
64
77
  function writeStatusOutput(statuses, context) {
65
78
  for (const status of statuses) {
@@ -4,11 +4,21 @@ const REQUIRED_README_SNIPPETS = [
4
4
  'ankh devtools knip',
5
5
  'ankh devtools sync',
6
6
  'ankh devtools status',
7
+ 'ankh devtools eslint sync',
8
+ 'ankh devtools prettier sync',
9
+ 'ankh devtools knip sync',
10
+ 'ankh devtools package sync',
7
11
  'ankh devtools workflows sync',
8
12
  'ankh devtools vscode sync',
13
+ 'devtools.eslint.sync',
14
+ 'devtools.prettier.sync',
15
+ 'devtools.knip.sync',
16
+ 'devtools.package.sync',
9
17
  'devtools.workflows.sync',
10
18
  'devtools.vscode.sync',
11
19
  '--dry-run',
20
+ "profile: 'auto'",
21
+ '@ankhorage/utility/project',
12
22
  ];
13
23
  export function getReadmeDocumentationErrors(readmeContents) {
14
24
  return REQUIRED_README_SNIPPETS.flatMap((snippet) => readmeContents.includes(snippet)
@@ -6,4 +6,4 @@ export declare const defaultRestrictedImports: readonly [{
6
6
  readonly message: "Forbidden in Ankhorage packages. Use '@ankhorage/react-native-reanimated-dnd-web' directly.";
7
7
  }];
8
8
  export declare function createConfig(options: DevtoolsConfigOptions): ReturnType<typeof tseslint.config>;
9
- export type { DevtoolsConfigOptions, FlatConfigItem } from './types.js';
9
+ export type { DevtoolsConfigOptions, DevtoolsEslintProfile, FlatConfigItem, ResolvedDevtoolsEslintProfile, RestrictedImport, } from './types.js';