@ankhorage/devtools 1.5.1 → 1.6.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.
@@ -1,11 +1,23 @@
1
1
  /**
2
- * Canonical Bun runtime policy for Ankhorage repositories.
2
+ * Canonical runtime/tooling policies for Ankhorage repositories.
3
3
  *
4
- * Import this from `@ankhorage/devtools/policy` when another package needs to inspect
5
- * the managed Bun version without defining an independent version authority.
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
6
  */
7
7
  export declare const bunRuntimePolicy: {
8
8
  readonly packageManager: "bun@1.3.14";
9
9
  readonly typesRange: "^1.3.14";
10
10
  readonly version: "1.3.14";
11
11
  };
12
+ /**
13
+ * Canonical Node LTS baseline for Node-based Ankhorage tooling and CI execution.
14
+ *
15
+ * Bun remains the repository package manager/primary command runtime where configured;
16
+ * this policy makes Node-based tooling deterministic instead of inheriting the ambient
17
+ * Node version from a CI runner image.
18
+ */
19
+ export declare const nodeRuntimePolicy: {
20
+ readonly engineRange: "24.x";
21
+ readonly major: 24;
22
+ readonly setupVersion: "24";
23
+ };
@@ -1,11 +1,23 @@
1
1
  /**
2
- * Canonical Bun runtime policy for Ankhorage repositories.
2
+ * Canonical runtime/tooling policies for Ankhorage repositories.
3
3
  *
4
- * Import this from `@ankhorage/devtools/policy` when another package needs to inspect
5
- * the managed Bun version without defining an independent version authority.
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
6
  */
7
7
  export const bunRuntimePolicy = {
8
8
  packageManager: 'bun@1.3.14',
9
9
  typesRange: '^1.3.14',
10
10
  version: '1.3.14',
11
11
  };
12
+ /**
13
+ * Canonical Node LTS baseline for Node-based Ankhorage tooling and CI execution.
14
+ *
15
+ * Bun remains the repository package manager/primary command runtime where configured;
16
+ * this policy makes Node-based tooling deterministic instead of inheriting the ambient
17
+ * Node version from a CI runner image.
18
+ */
19
+ export const nodeRuntimePolicy = {
20
+ engineRange: '24.x',
21
+ major: 24,
22
+ setupVersion: '24',
23
+ };
@@ -7,10 +7,8 @@
7
7
  *
8
8
  * Every profile includes the shared TypeScript, import, unused-import, Prettier, security, and
9
9
  * quality rules. The common quality limits are 50 effective lines per function, 300 effective
10
- * lines per file, and modified cyclomatic complexity 15. Forward exports are forbidden outside
11
- * declared package entrypoints and explicit `index.*` barrels so implementation files export only
12
- * symbols they own. React adds React and Hooks correctness rules; React Native composes the React
13
- * profile and adds focused React Native rules.
10
+ * lines per file, and modified cyclomatic complexity 15. React adds React and Hooks correctness
11
+ * rules; React Native composes the React profile and adds focused React Native rules.
14
12
  *
15
13
  * Repository-specific behavior stays additive: `additionalIgnores`, `restrictedImports`, and
16
14
  * `overrides` extend the central policy instead of replacing it. Narrow local overrides remain the
@@ -32,8 +30,6 @@ import securityPlugin from 'eslint-plugin-security';
32
30
  import simpleImportSort from 'eslint-plugin-simple-import-sort';
33
31
  import unusedImports from 'eslint-plugin-unused-imports';
34
32
  import tseslint from 'typescript-eslint';
35
- import { createModuleOwnershipConfig } from './moduleOwnership.js';
36
- import { resolvePackageEntrypointFiles } from './packageEntrypoints.js';
37
33
  import { resolveEslintProfile } from './profile.js';
38
34
  export const defaultIgnores = [
39
35
  '**/ios/**',
@@ -56,8 +52,7 @@ export const defaultRestrictedImports = [
56
52
  export function createConfig(options) {
57
53
  const normalized = normalizeOptions(options);
58
54
  const profile = resolveEslintProfile(options);
59
- const packageEntrypoints = resolvePackageEntrypointFiles(options);
60
- return defineConfig({ ignores: [...defaultIgnores, ...normalized.additionalIgnores] }, { ...js.configs.recommended, files: normalized.files }, ...createTypeCheckedConfigs(normalized), createBaseConfig(normalized), createModuleOwnershipConfig(normalized.files, packageEntrypoints), ...createProfileConfigs(profile, normalized.files), ...normalized.overrides, ...(normalized.includePrettier ? [prettierConfig] : []));
55
+ return defineConfig({ ignores: [...defaultIgnores, ...normalized.additionalIgnores] }, { ...js.configs.recommended, files: normalized.files }, ...createTypeCheckedConfigs(normalized), createBaseConfig(normalized), ...createProfileConfigs(profile, normalized.files), ...normalized.overrides, ...(normalized.includePrettier ? [prettierConfig] : []));
61
56
  }
62
57
  function normalizeOptions(options) {
63
58
  return {
@@ -24,6 +24,11 @@ jobs:
24
24
  with:
25
25
  bun-version: '__ANKH_BUN_VERSION__'
26
26
 
27
+ - name: Setup Node
28
+ uses: actions/setup-node@v4
29
+ with:
30
+ node-version: '__ANKH_NODE_VERSION__'
31
+
27
32
  - name: Install dependencies
28
33
  run: bun install --frozen-lockfile
29
34
 
@@ -32,7 +32,7 @@ jobs:
32
32
  - name: Setup Node for npm publishing
33
33
  uses: actions/setup-node@v4
34
34
  with:
35
- node-version: 24
35
+ node-version: '__ANKH_NODE_VERSION__'
36
36
  registry-url: https://registry.npmjs.org
37
37
 
38
38
  - name: Update npm
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { bunRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
2
+ import { bunRuntimePolicy, nodeRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
3
3
  const BUN_VERSION_TOKEN = '__ANKH_BUN_VERSION__';
4
+ const NODE_VERSION_TOKEN = '__ANKH_NODE_VERSION__';
4
5
  export const workflowManagedFiles = [
5
6
  createWorkflowDefinition('.github/workflows/ci.yml', './files/ci.yml'),
6
7
  createWorkflowDefinition('.github/workflows/release.yml', './files/release.yml'),
@@ -11,7 +12,9 @@ function createWorkflowDefinition(relativePath, sourcePath) {
11
12
  relativePath,
12
13
  render: async () => {
13
14
  const template = await readFile(sourceUrl, 'utf8');
14
- return template.replaceAll(BUN_VERSION_TOKEN, bunRuntimePolicy.version);
15
+ return template
16
+ .replaceAll(BUN_VERSION_TOKEN, bunRuntimePolicy.version)
17
+ .replaceAll(NODE_VERSION_TOKEN, nodeRuntimePolicy.setupVersion);
15
18
  },
16
19
  };
17
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Shared development tools and repository standards for Ankhorage",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",
@@ -1,2 +0,0 @@
1
- import type { FlatConfigItem } from './types.js';
2
- export declare function createModuleOwnershipConfig(files: string[], packageEntrypoints?: string[]): FlatConfigItem;
@@ -1,64 +0,0 @@
1
- import { basename, normalize } from 'node:path';
2
- const INDEX_BARREL_FILE = /^index\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/u;
3
- function isForwardExport(context, node) {
4
- const tokens = context.sourceCode.getTokens(node);
5
- if (tokens.at(0)?.value !== 'export')
6
- return false;
7
- return tokens.some((token, index) => token.value === 'from' && tokens.at(index + 1)?.type === 'String');
8
- }
9
- function isAllowedForwardExportFile(context) {
10
- const filename = normalize(context.filename);
11
- if (INDEX_BARREL_FILE.test(basename(filename)))
12
- return true;
13
- return readAllowedFiles(context.options.at(0)).some((file) => normalize(file) === filename);
14
- }
15
- function readAllowedFiles(value) {
16
- if (!isRecord(value))
17
- return [];
18
- const { allowedFiles } = value;
19
- if (!Array.isArray(allowedFiles))
20
- return [];
21
- return allowedFiles.filter((file) => typeof file === 'string');
22
- }
23
- function isRecord(value) {
24
- return typeof value === 'object' && value !== null && !Array.isArray(value);
25
- }
26
- const noForwardExportsRule = {
27
- meta: {
28
- type: 'problem',
29
- schema: [
30
- {
31
- type: 'object',
32
- properties: {
33
- allowedFiles: { type: 'array', items: { type: 'string' } },
34
- },
35
- additionalProperties: false,
36
- },
37
- ],
38
- messages: {
39
- forwardExport: 'Forward exports are forbidden outside package entrypoints and index barrels. Import directly from the owning module.',
40
- },
41
- },
42
- create(context) {
43
- if (isAllowedForwardExportFile(context))
44
- return {};
45
- return {
46
- 'Program > *'(node) {
47
- if (isForwardExport(context, node))
48
- context.report({ node, messageId: 'forwardExport' });
49
- },
50
- };
51
- },
52
- };
53
- const moduleOwnershipPlugin = {
54
- rules: { 'no-forward-exports': noForwardExportsRule },
55
- };
56
- export function createModuleOwnershipConfig(files, packageEntrypoints = []) {
57
- return {
58
- files,
59
- plugins: { ankhorage: moduleOwnershipPlugin },
60
- rules: {
61
- 'ankhorage/no-forward-exports': ['error', { allowedFiles: packageEntrypoints }],
62
- },
63
- };
64
- }
@@ -1,2 +0,0 @@
1
- import type { DevtoolsConfigOptions } from './types.js';
2
- export declare function resolvePackageEntrypointFiles(options: DevtoolsConfigOptions): string[];
@@ -1,47 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
- import { resolveProjectPackageJsonPath } from './packageJsonPath.js';
4
- const SOURCE_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'mts', 'cts', 'mjs', 'cjs'];
5
- const OUTPUT_EXTENSION = /(?:\.d)?\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/u;
6
- export function resolvePackageEntrypointFiles(options) {
7
- const packageJsonPath = resolveProjectPackageJsonPath(options);
8
- if (packageJsonPath === null)
9
- return [];
10
- const packageJson = readPackageJson(packageJsonPath);
11
- const targets = [
12
- ...collectStringTargets(packageJson.main),
13
- ...collectStringTargets(packageJson.types),
14
- ...collectStringTargets(packageJson.exports),
15
- ];
16
- return [...new Set(targets.flatMap((target) => toSourceCandidates(target, packageJsonPath)))];
17
- }
18
- function readPackageJson(packageJsonPath) {
19
- const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
20
- if (!isRecord(parsed)) {
21
- throw new Error(`Expected package.json to contain a JSON object: ${packageJsonPath}`);
22
- }
23
- return parsed;
24
- }
25
- function collectStringTargets(value) {
26
- if (typeof value === 'string')
27
- return [value];
28
- if (Array.isArray(value))
29
- return value.flatMap(collectStringTargets);
30
- if (!isRecord(value))
31
- return [];
32
- return Object.values(value).flatMap(collectStringTargets);
33
- }
34
- function toSourceCandidates(target, packageJsonPath) {
35
- const normalizedTarget = target.replace(/^\.\//u, '');
36
- const sourceTarget = normalizedTarget.startsWith('dist/')
37
- ? `src/${normalizedTarget.slice('dist/'.length)}`
38
- : normalizedTarget;
39
- if (!sourceTarget.startsWith('src/'))
40
- return [];
41
- const sourceBase = sourceTarget.replace(OUTPUT_EXTENSION, '');
42
- const absoluteBase = resolve(dirname(packageJsonPath), sourceBase);
43
- return SOURCE_EXTENSIONS.map((extension) => `${absoluteBase}.${extension}`);
44
- }
45
- function isRecord(value) {
46
- return typeof value === 'object' && value !== null && !Array.isArray(value);
47
- }