@ankhorage/devtools 1.7.0 → 1.7.2

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
@@ -258,13 +258,17 @@ export default createKnipConfig();
258
258
 
259
259
  The canonical Bun policy is defined once in devtools and consumed by both package and workflow synchronization. The current policy is:
260
260
 
261
+ <!-- devtools-bun-policy:start -->
262
+
261
263
  ```text
262
- Bun runtime 1.3.14
263
- packageManager bun@1.3.14
264
- @types/bun ^1.3.14
264
+ Bun runtime 1.4.0
265
+ packageManager bun@1.4.0
266
+ @types/bun ^1.4.0
265
267
  ```
266
268
 
267
- Changing the policy in devtools therefore updates the repository-facing Bun version consistently instead of maintaining independent version literals in multiple templates.
269
+ <!-- devtools-bun-policy:end -->
270
+
271
+ Renovate owns the single `BUN_VERSION` literal in `src/policy/bunRuntimePolicy.ts`. Its trusted base-branch workflow invokes `bun scripts/sync-renovate-owner.ts sync repository` to regenerate `packageManager`, `@types/bun`, the Bun workflow setup versions, this documentation block, and `bun.lock`, then runs `bun scripts/sync-renovate-owner.ts status repository` to reject stale generated artifacts. Do not synchronize those values manually in a Renovate branch.
268
272
 
269
273
  ## Managed package contract
270
274
 
@@ -279,7 +283,7 @@ It owns:
279
283
  - `lint:fix`
280
284
  - `format`
281
285
  - `format:check`
282
- - `knip`
286
+ - `knip:check`
283
287
 
284
288
  For normal consumers, `@ankhorage/devtools` is a devDependency. `@ankhorage/ankh` keeps devtools as a runtime dependency because it loads the provider. Devtools itself participates in the Bun runtime policy without attempting to install itself as a consumer dependency.
285
289
 
@@ -4,12 +4,12 @@ declare const provider: {
4
4
  version: string;
5
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
- path: [string, ...string[]];
7
+ path: string[];
8
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: {
12
- path: [string, ...string[]];
12
+ path: string[];
13
13
  handler: (request: import("@ankhorage/ankh").AnkhCommandExecutionRequest) => Promise<{
14
14
  readonly exitCode: number;
15
15
  }>;
@@ -1,3 +1,4 @@
1
+ import { bunRuntimePolicy } from '../policy/bunRuntimePolicy.js';
1
2
  const REQUIRED_README_SNIPPETS = [
2
3
  'ankh devtools lint',
3
4
  'ankh devtools format',
@@ -19,6 +20,13 @@ const REQUIRED_README_SNIPPETS = [
19
20
  '--dry-run',
20
21
  "profile: 'auto'",
21
22
  '@ankhorage/utility/project',
23
+ '<!-- devtools-bun-policy:start -->',
24
+ '<!-- devtools-bun-policy:end -->',
25
+ 'bun scripts/sync-renovate-owner.ts sync repository',
26
+ 'bun scripts/sync-renovate-owner.ts status',
27
+ bunRuntimePolicy.version,
28
+ bunRuntimePolicy.packageManager,
29
+ bunRuntimePolicy.typesRange,
22
30
  ];
23
31
  export function getReadmeDocumentationErrors(readmeContents) {
24
32
  return REQUIRED_README_SNIPPETS.flatMap((snippet) => readmeContents.includes(snippet)
@@ -0,0 +1,6 @@
1
+ export declare function synchronizeRenovateOwnerAsync(operation: OwnerSyncOperation, targetDirectory: string, options?: OwnerSyncOptions): Promise<void>;
2
+ type OwnerSyncOperation = 'status' | 'sync';
3
+ interface OwnerSyncOptions {
4
+ readonly runLockfileAsync?: (operation: OwnerSyncOperation, targetDirectory: string) => Promise<void>;
5
+ }
6
+ export {};
@@ -0,0 +1,153 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { applyBunRuntimePolicy } from '../policy/applyBunRuntimePolicy.js';
5
+ import { nodeRuntimePolicy } from '../policy/bunRuntimePolicy.js';
6
+ import { renderWorkflowAsync } from '../tools/workflows/renderWorkflowAsync.js';
7
+ export async function synchronizeRenovateOwnerAsync(operation, targetDirectory, options = {}) {
8
+ const target = resolve(targetDirectory);
9
+ const policy = await readTargetBunPolicyAsync(target);
10
+ const definitions = await createManagedDefinitionsAsync(target, policy);
11
+ await assertDevtoolsTargetAsync(target);
12
+ if (operation === 'sync') {
13
+ await syncDefinitionsAsync(target, definitions);
14
+ await (options.runLockfileAsync ?? runBunLockfileAsync)(operation, target);
15
+ }
16
+ const outdatedPaths = await getOutdatedPathsAsync(target, definitions);
17
+ if (outdatedPaths.length > 0) {
18
+ throw new Error(`Stale Devtools owner policy artifacts: ${outdatedPaths.join(', ')}`);
19
+ }
20
+ if (operation === 'status') {
21
+ await (options.runLockfileAsync ?? runBunLockfileAsync)(operation, target);
22
+ }
23
+ }
24
+ async function assertDevtoolsTargetAsync(targetDirectory) {
25
+ const manifest = JSON.parse(await readFile(resolve(targetDirectory, 'package.json'), 'utf8'));
26
+ if (!isRecord(manifest) || manifest.name !== '@ankhorage/devtools') {
27
+ throw new Error('The Renovate owner sync target must be @ankhorage/devtools.');
28
+ }
29
+ }
30
+ async function createManagedDefinitionsAsync(targetDirectory, policy) {
31
+ const manifest = JSON.parse(await readFile(resolve(targetDirectory, 'package.json'), 'utf8'));
32
+ if (!isRecord(manifest)) {
33
+ throw new Error('Devtools package.json must contain a JSON object.');
34
+ }
35
+ const readme = await readFile(resolve(targetDirectory, 'README.md'), 'utf8');
36
+ const workflowPolicy = {
37
+ bunVersion: policy.version,
38
+ nodeVersion: nodeRuntimePolicy.setupVersion,
39
+ };
40
+ return [
41
+ {
42
+ relativePath: 'package.json',
43
+ contents: serializePackageManifest(applyBunRuntimePolicy(manifest, policy)),
44
+ },
45
+ {
46
+ relativePath: '.github/workflows/ci.yml',
47
+ contents: await renderWorkflowAsync(new URL('../tools/workflows/files/ci.yml', import.meta.url), workflowPolicy),
48
+ },
49
+ {
50
+ relativePath: '.github/workflows/release.yml',
51
+ contents: await renderWorkflowAsync(new URL('../tools/workflows/files/release.yml', import.meta.url), workflowPolicy),
52
+ },
53
+ {
54
+ relativePath: '.github/workflows/renovate.yml',
55
+ contents: await renderWorkflowAsync(new URL('../tools/workflows/files/renovate.yml', import.meta.url), workflowPolicy),
56
+ },
57
+ {
58
+ relativePath: 'README.md',
59
+ contents: replaceReadmePolicy(readme, policy),
60
+ },
61
+ ];
62
+ }
63
+ async function getOutdatedPathsAsync(targetDirectory, definitions) {
64
+ const results = await Promise.all(definitions.map(async ({ contents, relativePath }) => {
65
+ const current = await readFile(resolve(targetDirectory, relativePath), 'utf8').catch((error) => {
66
+ if (isNodeError(error) && error.code === 'ENOENT')
67
+ return null;
68
+ throw error;
69
+ });
70
+ return current === contents ? null : relativePath;
71
+ }));
72
+ return results.filter((relativePath) => relativePath !== null);
73
+ }
74
+ function isNodeError(error) {
75
+ return error instanceof Error && 'code' in error;
76
+ }
77
+ function isRecord(value) {
78
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
79
+ }
80
+ async function readTargetBunPolicyAsync(targetDirectory) {
81
+ const contents = await readFile(resolve(targetDirectory, 'src/policy/bunRuntimePolicy.ts'), 'utf8');
82
+ const matches = [...contents.matchAll(BUN_VERSION_PATTERN)];
83
+ const version = matches.length === 1 ? matches[0]?.[1] : undefined;
84
+ if (version === undefined) {
85
+ throw new Error('Expected exactly one canonical BUN_VERSION literal in the target policy.');
86
+ }
87
+ return {
88
+ packageManager: `bun@${version}`,
89
+ typesRange: `^${version}`,
90
+ version,
91
+ };
92
+ }
93
+ function replaceReadmePolicy(readme, policy) {
94
+ const startIndex = readme.indexOf(README_POLICY_START);
95
+ const endIndex = readme.indexOf(README_POLICY_END);
96
+ if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
97
+ throw new Error('README.md must contain one ordered Devtools Bun policy marker pair.');
98
+ }
99
+ if (readme.includes(README_POLICY_START, startIndex + README_POLICY_START.length) ||
100
+ readme.includes(README_POLICY_END, endIndex + README_POLICY_END.length)) {
101
+ throw new Error('README.md must contain exactly one Devtools Bun policy marker pair.');
102
+ }
103
+ const replacement = `${README_POLICY_START}\n\n${renderReadmePolicy(policy)}\n\n${README_POLICY_END}`;
104
+ return `${readme.slice(0, startIndex)}${replacement}${readme.slice(endIndex + README_POLICY_END.length)}`;
105
+ }
106
+ function renderReadmePolicy(policy) {
107
+ return `\`\`\`text
108
+ Bun runtime ${policy.version}
109
+ packageManager ${policy.packageManager}
110
+ @types/bun ${policy.typesRange}
111
+ \`\`\``;
112
+ }
113
+ async function runBunLockfileAsync(operation, targetDirectory) {
114
+ const args = [
115
+ 'install',
116
+ '--cwd',
117
+ targetDirectory,
118
+ '--ignore-scripts',
119
+ '--lockfile-only',
120
+ '--registry=https://registry.npmjs.org',
121
+ ];
122
+ if (operation === 'status')
123
+ args.push('--frozen-lockfile');
124
+ await new Promise((resolvePromise, rejectPromise) => {
125
+ const child = spawn('bun', args, { stdio: 'inherit' });
126
+ child.once('error', rejectPromise);
127
+ child.once('exit', (code) => {
128
+ if (code === 0)
129
+ return resolvePromise();
130
+ rejectPromise(new Error(`Bun lockfile ${operation} exited with code ${code ?? 'unknown'}.`));
131
+ });
132
+ });
133
+ }
134
+ function serializePackageManifest(manifest) {
135
+ return `${JSON.stringify(manifest, null, 2)}\n`;
136
+ }
137
+ async function syncDefinitionsAsync(targetDirectory, definitions) {
138
+ for (const { contents, relativePath } of definitions) {
139
+ const targetPath = resolve(targetDirectory, relativePath);
140
+ const current = await readFile(targetPath, 'utf8').catch((error) => {
141
+ if (isNodeError(error) && error.code === 'ENOENT')
142
+ return null;
143
+ throw error;
144
+ });
145
+ if (current === contents)
146
+ continue;
147
+ await mkdir(dirname(targetPath), { recursive: true });
148
+ await writeFile(targetPath, contents, 'utf8');
149
+ }
150
+ }
151
+ const BUN_VERSION_PATTERN = /const BUN_VERSION = '(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)';/gu;
152
+ const README_POLICY_END = '<!-- devtools-bun-policy:end -->';
153
+ const README_POLICY_START = '<!-- devtools-bun-policy:start -->';
@@ -0,0 +1,4 @@
1
+ export declare function applyBunRuntimePolicy(manifest: Record<string, unknown>, policy: {
2
+ readonly packageManager: string;
3
+ readonly typesRange: string;
4
+ }): Record<string, unknown>;
@@ -0,0 +1,15 @@
1
+ export function applyBunRuntimePolicy(manifest, policy) {
2
+ const devDependencies = toRecord(manifest.devDependencies);
3
+ devDependencies['@types/bun'] = policy.typesRange;
4
+ return {
5
+ ...manifest,
6
+ packageManager: policy.packageManager,
7
+ devDependencies,
8
+ };
9
+ }
10
+ function isRecord(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ function toRecord(value) {
14
+ return isRecord(value) ? { ...value } : {};
15
+ }
@@ -1,13 +1,7 @@
1
- /**
2
- * Canonical runtime/tooling policies for Ankhorage repositories.
3
- *
4
- * Import these from `@ankhorage/devtools/policy` when another package needs to inspect
5
- * the managed Bun or Node baseline without defining an independent version authority.
6
- */
7
1
  export declare const bunRuntimePolicy: {
8
- readonly packageManager: "bun@1.3.14";
9
- readonly typesRange: "^1.3.14";
10
- readonly version: "1.3.14";
2
+ readonly packageManager: "bun@1.4.0";
3
+ readonly typesRange: "^1.4.0";
4
+ readonly version: "1.4.0";
11
5
  };
12
6
  /**
13
7
  * Canonical Node LTS baseline for Node-based Ankhorage tooling and CI execution.
@@ -4,10 +4,11 @@
4
4
  * Import these from `@ankhorage/devtools/policy` when another package needs to inspect
5
5
  * the managed Bun or Node baseline without defining an independent version authority.
6
6
  */
7
+ const BUN_VERSION = '1.4.0';
7
8
  export const bunRuntimePolicy = {
8
- packageManager: 'bun@1.3.14',
9
- typesRange: '^1.3.14',
10
- version: '1.3.14',
9
+ packageManager: `bun@${BUN_VERSION}`,
10
+ typesRange: `^${BUN_VERSION}`,
11
+ version: BUN_VERSION,
11
12
  };
12
13
  /**
13
14
  * Canonical Node LTS baseline for Node-based Ankhorage tooling and CI execution.
@@ -20,6 +20,7 @@
20
20
  import { readFileSync } from 'node:fs';
21
21
  import { readFile, writeFile } from 'node:fs/promises';
22
22
  import { resolve } from 'node:path';
23
+ import { applyBunRuntimePolicy } from '../../policy/applyBunRuntimePolicy.js';
23
24
  import { bunRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
24
25
  const PACKAGE_PATH = 'package.json';
25
26
  const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
@@ -80,7 +81,7 @@ export async function syncPackageManifest(targetDirectory, devtoolsVersion, opti
80
81
  }
81
82
  export function applyManagedPackageContract(manifest, devtoolsVersion) {
82
83
  if (manifest.name === DEVTOOLS_PACKAGE_NAME) {
83
- return applyBunRuntimePolicy(manifest);
84
+ return applyBunRuntimePolicy(manifest, bunRuntimePolicy);
84
85
  }
85
86
  const scripts = { ...toRecord(manifest.scripts) };
86
87
  delete scripts.knip;
@@ -93,7 +94,7 @@ export function applyManagedPackageContract(manifest, devtoolsVersion) {
93
94
  ...normalizedDependencies(manifest, dependencies),
94
95
  scripts,
95
96
  devDependencies,
96
- });
97
+ }, bunRuntimePolicy);
97
98
  }
98
99
  export function isManagedPackageContractCurrent(manifest, devtoolsVersion) {
99
100
  if (!hasCurrentBunRuntimePolicy(manifest)) {
@@ -132,15 +133,6 @@ async function readPackageManifest(targetDirectory) {
132
133
  throw error;
133
134
  }
134
135
  }
135
- function applyBunRuntimePolicy(manifest) {
136
- const devDependencies = toRecord(manifest.devDependencies);
137
- devDependencies[BUN_TYPES_PACKAGE_NAME] = bunRuntimePolicy.typesRange;
138
- return {
139
- ...manifest,
140
- packageManager: bunRuntimePolicy.packageManager,
141
- devDependencies,
142
- };
143
- }
144
136
  function hasCurrentBunRuntimePolicy(manifest) {
145
137
  const devDependencies = toRecord(manifest.devDependencies);
146
138
  return (manifest.packageManager === bunRuntimePolicy.packageManager &&
@@ -18,4 +18,4 @@ jobs:
18
18
  github.actor == 'renovate[bot]' &&
19
19
  github.event.pull_request.head.repo.full_name == github.repository &&
20
20
  startsWith(github.event.pull_request.head.ref, 'renovate/')
21
- uses: ankhorage/renovate/.github/workflows/changeset.yml@b7305e8f17f9b07238f6b827bbc9f866fd498a0f
21
+ uses: ankhorage/renovate/.github/workflows/changeset.yml@7d4a5104b94e763ca5be34919f4fcfbb12efd526
@@ -1,7 +1,5 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { bunRuntimePolicy, nodeRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
3
- const BUN_VERSION_TOKEN = '__ANKH_BUN_VERSION__';
4
- const NODE_VERSION_TOKEN = '__ANKH_NODE_VERSION__';
2
+ import { renderWorkflowAsync } from './renderWorkflowAsync.js';
5
3
  export const workflowManagedFiles = [
6
4
  createWorkflowDefinition('.github/workflows/ci.yml', './files/ci.yml'),
7
5
  createWorkflowDefinition('.github/workflows/release.yml', './files/release.yml'),
@@ -11,11 +9,9 @@ function createWorkflowDefinition(relativePath, sourcePath) {
11
9
  const sourceUrl = new URL(sourcePath, import.meta.url);
12
10
  return {
13
11
  relativePath,
14
- render: async () => {
15
- const template = await readFile(sourceUrl, 'utf8');
16
- return template
17
- .replaceAll(BUN_VERSION_TOKEN, bunRuntimePolicy.version)
18
- .replaceAll(NODE_VERSION_TOKEN, nodeRuntimePolicy.setupVersion);
19
- },
12
+ render: async () => await renderWorkflowAsync(sourceUrl, {
13
+ bunVersion: bunRuntimePolicy.version,
14
+ nodeVersion: nodeRuntimePolicy.setupVersion,
15
+ }),
20
16
  };
21
17
  }
@@ -0,0 +1,4 @@
1
+ export declare function renderWorkflowAsync(sourceUrl: URL, policy: {
2
+ readonly bunVersion: string;
3
+ readonly nodeVersion: string;
4
+ }): Promise<string>;
@@ -0,0 +1,9 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ export async function renderWorkflowAsync(sourceUrl, policy) {
3
+ const template = await readFile(sourceUrl, 'utf8');
4
+ return template
5
+ .replaceAll(BUN_VERSION_TOKEN, policy.bunVersion)
6
+ .replaceAll(NODE_VERSION_TOKEN, policy.nodeVersion);
7
+ }
8
+ const BUN_VERSION_TOKEN = '__ANKH_BUN_VERSION__';
9
+ const NODE_VERSION_TOKEN = '__ANKH_NODE_VERSION__';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Shared development tools and repository standards for Ankhorage",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",
@@ -86,7 +86,7 @@
86
86
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc && mkdir -p dist/tools/prettier dist/tools/workflows dist/tools/vscode && cp src/tools/prettier/index.cjs dist/tools/prettier/index.cjs && cp -R src/tools/workflows/files dist/tools/workflows/files && cp -R src/tools/vscode/files dist/tools/vscode/files",
87
87
  "typecheck": "bun x tsc --noEmit -p tsconfig.test.json",
88
88
  "doctor": "ankhorage-doctor validate .",
89
- "knip": "knip",
89
+ "knip:check": "knip",
90
90
  "lint": "eslint .",
91
91
  "lint:fix": "eslint . --fix --max-warnings=0",
92
92
  "format": "prettier --write .",
@@ -99,30 +99,30 @@
99
99
  "version-packages": "changeset version"
100
100
  },
101
101
  "dependencies": {
102
- "@ankhorage/utility": "^0.1.1",
102
+ "@ankhorage/utility": "^0.2.0",
103
103
  "@eslint/compat": "^2.1.0",
104
104
  "@eslint/js": "^10.0.1",
105
- "eslint": "^10.7.0",
105
+ "eslint": "^10.9.1",
106
106
  "eslint-config-prettier": "^10.1.8",
107
107
  "eslint-plugin-import": "^2.32.0",
108
- "eslint-plugin-prettier": "^5.5.5",
108
+ "eslint-plugin-prettier": "^5.5.6",
109
109
  "eslint-plugin-react": "^7.37.5",
110
110
  "eslint-plugin-react-hooks": "^7.1.1",
111
111
  "eslint-plugin-react-native": "^5.0.0",
112
112
  "eslint-plugin-security": "^4.0.1",
113
113
  "eslint-plugin-simple-import-sort": "^12.1.1",
114
114
  "eslint-plugin-unused-imports": "^4.4.1",
115
- "knip": "^6.12.2",
116
- "prettier": "^3.8.1",
117
- "typescript-eslint": "^8.24.0"
115
+ "knip": "^6.32.3",
116
+ "prettier": "^3.9.6",
117
+ "typescript-eslint": "^8.68.0"
118
118
  },
119
119
  "devDependencies": {
120
- "@ankhorage/ankh": "^0.4.0",
121
- "@ankhorage/doctor": "0.3.0",
122
- "@changesets/cli": "^2.30.0",
123
- "@types/bun": "^1.3.14",
124
- "@types/node": "^25.2.3",
120
+ "@ankhorage/ankh": "^0.8.2",
121
+ "@ankhorage/doctor": "0.10.1",
122
+ "@changesets/cli": "^2.31.1",
123
+ "@types/bun": "^1.4.0",
124
+ "@types/node": "^25.9.5",
125
125
  "typescript": "^5.9.3"
126
126
  },
127
- "packageManager": "bun@1.3.14"
127
+ "packageManager": "bun@1.4.0"
128
128
  }