From 4fc25ea04b0648004b9ec5701549c42b0a4c0201 Mon Sep 17 00:00:00 2001 From: Joey Farina Date: Mon, 19 Oct 2026 23:00:15 -0700 Subject: [PATCH 1/4] fix(cli): a TypeScript the checks cannot use is named with the fix When the project's TypeScript is missing, older than a check reads, or has no JavaScript compiler API (TypeScript 7), the skip reason of the doc-type and prop checks now names the version and what to do: install TypeScript 5.0 through 6 (`npm install -D typescript@6`). `integration check --strict` fails with that message. --- .changeset/typescript-fix-in-skip-reasons.md | 6 ++++++ packages/cli/foundation/discovery/doc-types.mjs | 6 +++--- packages/cli/foundation/discovery/doc-types.test.mjs | 4 ++++ .../cli/foundation/discovery/project-typescript.mjs | 12 ++++++++++-- packages/cli/foundation/discovery/prop-drift.mjs | 4 ++-- .../cli/foundation/discovery/prop-drift.test.mjs | 9 +++++++-- packages/cli/foundation/discovery/type-text.mjs | 2 +- packages/cli/foundation/discovery/type-text.test.mjs | 2 +- 8 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 .changeset/typescript-fix-in-skip-reasons.md diff --git a/.changeset/typescript-fix-in-skip-reasons.md b/.changeset/typescript-fix-in-skip-reasons.md new file mode 100644 index 0000000000000..49b5f929ed011 --- /dev/null +++ b/.changeset/typescript-fix-in-skip-reasons.md @@ -0,0 +1,6 @@ +--- +'@astryxdesign/cli': patch +--- + +[fix] When the project's TypeScript is missing, too old, or has no JavaScript compiler API (TypeScript 7), `integration check` and `doctor integration validate|components` now name the version and the fix in the skip reason: install TypeScript 5.0 through 6 (`npm install -D typescript@6`). `--strict` fails with that message instead of a bare skip. +@josephfarina diff --git a/packages/cli/foundation/discovery/doc-types.mjs b/packages/cli/foundation/discovery/doc-types.mjs index 7c9452b0bd7f0..9dd5604ec5439 100644 --- a/packages/cli/foundation/discovery/doc-types.mjs +++ b/packages/cli/foundation/discovery/doc-types.mjs @@ -41,6 +41,7 @@ import * as path from 'node:path'; import {fileURLToPath} from 'node:url'; import { resolveProjectTypeScript, + TYPESCRIPT_FIX, unusableTypeScriptReason, } from './project-typescript.mjs'; @@ -91,8 +92,7 @@ import { */ /** The line both surfaces print when the compiler is missing. */ -export const DOC_TYPES_TYPESCRIPT_MISSING_REASON = - 'TypeScript is not installed in this project, so docs were not checked against the authoring types. Install typescript and run this check again.'; +export const DOC_TYPES_TYPESCRIPT_MISSING_REASON = `TypeScript is not installed in this project, so docs were not checked against the authoring types. ${TYPESCRIPT_FIX}`; const AUTHORING = '@astryxdesign/cli/authoring'; const AUTHORING_TYPES = path.resolve( @@ -933,7 +933,7 @@ export async function checkDocTypes(files, options) { if (major < needMajor || (major === needMajor && minor < needMinor)) { return { status: 'skipped', - reason: `TypeScript ${version} cannot check ${hasJs ? 'a JavaScript doc' : 'a doc'} against its type (that needs TypeScript ${needMajor}.${needMinor} or later), so docs were not checked. Upgrade typescript and run this check again.`, + reason: `TypeScript ${version} cannot check ${hasJs ? 'a JavaScript doc' : 'a doc'} against its type (that needs TypeScript ${needMajor}.${needMinor} or later), so docs were not checked. ${TYPESCRIPT_FIX}`, docs: 0, findings: [], unchecked: [], diff --git a/packages/cli/foundation/discovery/doc-types.test.mjs b/packages/cli/foundation/discovery/doc-types.test.mjs index ebcb8a7f2727e..ae80a8fadcc7b 100644 --- a/packages/cli/foundation/discovery/doc-types.test.mjs +++ b/packages/cli/foundation/discovery/doc-types.test.mjs @@ -117,6 +117,10 @@ describe('checkDocTypes', () => { const report = await check([['components/Badge.doc.mjs', 'components']]); + // The reason names the version and the fix, so a --strict failure + // says what to install. + expect(report.reason).toContain('TypeScript 7.0.0'); + expect(report.reason).toContain('npm install -D typescript@6'); expect(report).toEqual({ status: 'skipped', reason: unusableTypeScriptReason('7.0.0', 'docs were not checked'), diff --git a/packages/cli/foundation/discovery/project-typescript.mjs b/packages/cli/foundation/discovery/project-typescript.mjs index a5108a0225135..fef861a71d30b 100644 --- a/packages/cli/foundation/discovery/project-typescript.mjs +++ b/packages/cli/foundation/discovery/project-typescript.mjs @@ -50,12 +50,20 @@ export function resolveProjectTypeScript(dirs) { return unusable ? {ts: null, version: unusable} : null; } +/** + * What to do when the project's TypeScript is missing or cannot be used: a + * version every check here reads, 5.0 (the oldest that checks a JavaScript + * doc) through 6 (the newest with a JavaScript compiler API). + */ +export const TYPESCRIPT_FIX = + 'Install TypeScript 5.0 through 6 (`npm install -D typescript@6`) and run this check again.'; + /** * Why a check that needs the compiler did not run, when the TypeScript - * installed has no JavaScript compiler API. + * installed has no JavaScript compiler API, and what to do. * @param {string} version * @param {string} outcome what was not done, such as `docs were not checked` */ export function unusableTypeScriptReason(version, outcome) { - return `TypeScript ${version} is installed in this project, but it has no JavaScript compiler API (\`createProgram\`) to read types with, so ${outcome}.`; + return `TypeScript ${version} is installed in this project, but it has no JavaScript compiler API (\`createProgram\`) to read types with (TypeScript 7 ships none), so ${outcome}. ${TYPESCRIPT_FIX}`; } diff --git a/packages/cli/foundation/discovery/prop-drift.mjs b/packages/cli/foundation/discovery/prop-drift.mjs index 206238fda6a93..5a9f2b3b29f51 100644 --- a/packages/cli/foundation/discovery/prop-drift.mjs +++ b/packages/cli/foundation/discovery/prop-drift.mjs @@ -47,6 +47,7 @@ import {loadComponentDoc} from './component-loader.mjs'; import {readPackageTsconfig} from './package-tsconfig.mjs'; import { resolveProjectTypeScript, + TYPESCRIPT_FIX, unusableTypeScriptReason, } from './project-typescript.mjs'; import {judgeType} from './type-match.mjs'; @@ -158,8 +159,7 @@ const outside = (name, code) => const HOOK_NAME = /^use[A-Z0-9]/; /** The line both surfaces print when the compiler is missing. */ -export const TYPESCRIPT_MISSING_REASON = - 'TypeScript is not installed in this project, so documented props were not compared with the code. Install typescript and run this check again.'; +export const TYPESCRIPT_MISSING_REASON = `TypeScript is not installed in this project, so documented props were not compared with the code. ${TYPESCRIPT_FIX}`; /** * @template T diff --git a/packages/cli/foundation/discovery/prop-drift.test.mjs b/packages/cli/foundation/discovery/prop-drift.test.mjs index 5ef1d19505fff..bc98ddcdcd883 100644 --- a/packages/cli/foundation/discovery/prop-drift.test.mjs +++ b/packages/cli/foundation/discovery/prop-drift.test.mjs @@ -176,7 +176,8 @@ describe('comparePropTables', () => { expect(await compare([component('Badge', source, doc)])).toMatchObject({ findings: [], typesCompared: 0, - typesSkipped: `TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads`, + typesSkipped: + 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (`npm install -D typescript@6`) to judge them', }); }); @@ -2196,6 +2197,7 @@ export const Hoc = withName((props: {label?: string}) => null);`; }); expect(await compare([badge])).toEqual(skipped(TYPESCRIPT_MISSING_REASON)); + expect(TYPESCRIPT_MISSING_REASON).toContain('npm install -D typescript@6'); // TypeScript 7 has no JavaScript compiler API to load. const dir = path.join(pkg, 'node_modules', 'typescript'); fs.mkdirSync(dir, {recursive: true}); @@ -2207,7 +2209,10 @@ export const Hoc = withName((props: {label?: string}) => null);`; exports: {'./package.json': './package.json'}, }), ); - expect(await compare([badge])).toEqual( + const report = await compare([badge]); + expect(report.reason).toContain('TypeScript 7.0.0'); + expect(report.reason).toContain('npm install -D typescript@6'); + expect(report).toEqual( skipped( unusableTypeScriptReason( '7.0.0', diff --git a/packages/cli/foundation/discovery/type-text.mjs b/packages/cli/foundation/discovery/type-text.mjs index 0f34101890c13..6facbee51bc93 100644 --- a/packages/cli/foundation/discovery/type-text.mjs +++ b/packages/cli/foundation/discovery/type-text.mjs @@ -46,7 +46,7 @@ export function typeTextTooOld(ts) { const [major, minor] = String(ts.version).split('.').map(Number); return major > 4 || (major === 4 && minor >= 8) ? null - : `TypeScript ${ts.version} is older than 4.8, the oldest the type comparison reads`; + : `TypeScript ${ts.version} is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (\`npm install -D typescript@6\`) to judge them`; } /** How tightly a printed type binds: a slot needs at least its level. */ diff --git a/packages/cli/foundation/discovery/type-text.test.mjs b/packages/cli/foundation/discovery/type-text.test.mjs index 4b3b53e83bb41..96dd7665a3d27 100644 --- a/packages/cli/foundation/discovery/type-text.test.mjs +++ b/packages/cli/foundation/discovery/type-text.test.mjs @@ -176,7 +176,7 @@ describe('normalizeTypeText', () => { 'string[]', ]); expect(typeTextTooOld({version: '4.7.4'})).toBe( - 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads', + 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (`npm install -D typescript@6`) to judge them', ); for (const version of ['4.8.4', '5.0.4', '6.0.3', ts.version]) { expect(typeTextTooOld({version})).toBeNull(); From 6680658ebadae222192a5dd047cdb48e82deecbf Mon Sep 17 00:00:00 2001 From: Joey Farina Date: Mon, 19 Oct 2026 23:41:12 -0700 Subject: [PATCH 2/4] feat(cli): on TypeScript 7, read types with its TypeScript 6 API package TypeScript 7 ships no JavaScript compiler API; TypeScript publishes `@typescript/typescript6` for tools that need one. Where the project's `typescript` has no `createProgram`, the checks now use that package when it is installed, and the remedy names it first. --- .changeset/typescript-fix-in-skip-reasons.md | 2 +- .../foundation/discovery/doc-types.test.mjs | 46 +++++++++++++++- .../discovery/project-typescript.mjs | 29 ++++++++--- .../foundation/discovery/prop-drift.test.mjs | 52 ++++++++++++++++++- .../cli/foundation/discovery/type-text.mjs | 2 +- .../foundation/discovery/type-text.test.mjs | 2 +- 6 files changed, 121 insertions(+), 12 deletions(-) diff --git a/.changeset/typescript-fix-in-skip-reasons.md b/.changeset/typescript-fix-in-skip-reasons.md index 49b5f929ed011..2af115fd46677 100644 --- a/.changeset/typescript-fix-in-skip-reasons.md +++ b/.changeset/typescript-fix-in-skip-reasons.md @@ -2,5 +2,5 @@ '@astryxdesign/cli': patch --- -[fix] When the project's TypeScript is missing, too old, or has no JavaScript compiler API (TypeScript 7), `integration check` and `doctor integration validate|components` now name the version and the fix in the skip reason: install TypeScript 5.0 through 6 (`npm install -D typescript@6`). `--strict` fails with that message instead of a bare skip. +[feat] On TypeScript 7, which ships no JavaScript compiler API, the doc-type and prop checks in `integration check` and `doctor integration validate|components` use the TypeScript 6 API package TypeScript publishes for tools (`@typescript/typescript6`) when the project installs it beside TypeScript 7. When no usable TypeScript is found, the skip reason now ends with the remedy: add `@typescript/typescript6` on TypeScript 7, or install TypeScript 5.0 through 6 (`npm install -D typescript@6`). @josephfarina diff --git a/packages/cli/foundation/discovery/doc-types.test.mjs b/packages/cli/foundation/discovery/doc-types.test.mjs index ae80a8fadcc7b..965a78a1b5078 100644 --- a/packages/cli/foundation/discovery/doc-types.test.mjs +++ b/packages/cli/foundation/discovery/doc-types.test.mjs @@ -40,6 +40,37 @@ function withTypeScript() { ); } +/** + * TypeScript 7 (no JavaScript compiler API) with the TypeScript 6 API package + * beside it, which re-exports a real TypeScript. + */ +function withTypeScript7AndItsTypeScript6Api() { + const ts7 = path.join(pkg, 'node_modules', 'typescript'); + fs.mkdirSync(ts7, {recursive: true}); + fs.writeFileSync( + path.join(ts7, 'package.json'), + JSON.stringify({ + name: 'typescript', + version: '7.0.2', + exports: {'./package.json': './package.json'}, + }), + ); + const ts6 = path.join(pkg, 'node_modules', '@typescript', 'typescript6'); + fs.mkdirSync(ts6, {recursive: true}); + fs.writeFileSync( + path.join(ts6, 'package.json'), + JSON.stringify({ + name: '@typescript/typescript6', + version: '6.0.2', + main: 'index.js', + }), + ); + fs.writeFileSync( + path.join(ts6, 'index.js'), + `module.exports = require(${JSON.stringify(TYPESCRIPT_DIR)});\n`, + ); +} + /** * @param {string} rel * @param {string} text @@ -120,7 +151,7 @@ describe('checkDocTypes', () => { // The reason names the version and the fix, so a --strict failure // says what to install. expect(report.reason).toContain('TypeScript 7.0.0'); - expect(report.reason).toContain('npm install -D typescript@6'); + expect(report.reason).toContain('npm install -D @typescript/typescript6'); expect(report).toEqual({ status: 'skipped', reason: unusableTypeScriptReason('7.0.0', 'docs were not checked'), @@ -132,6 +163,19 @@ describe('checkDocTypes', () => { } }); + it("checks with TypeScript 7's TypeScript 6 API package when the project has one", async () => { + write( + 'components/Badge.doc.mjs', + `export default ${JSON.stringify(COMPONENT)};\n`, + ); + withTypeScript7AndItsTypeScript6Api(); + + const report = await check([['components/Badge.doc.mjs', 'components']]); + + expect(report.status).toBe('checked'); + expect(report.docs).toBe(1); + }); + it('says there is nothing to check when the package has no doc files, with or without TypeScript', async () => { const without = await check([]); withTypeScript(); diff --git a/packages/cli/foundation/discovery/project-typescript.mjs b/packages/cli/foundation/discovery/project-typescript.mjs index fef861a71d30b..936707c44d49f 100644 --- a/packages/cli/foundation/discovery/project-typescript.mjs +++ b/packages/cli/foundation/discovery/project-typescript.mjs @@ -15,7 +15,15 @@ import * as path from 'node:path'; import {createRequire} from 'node:module'; /** - * Resolve the project's own `typescript`, trying each directory in order. + * TypeScript 7 has no JavaScript compiler API; the package TypeScript ships + * beside it for tools that need one carries TypeScript 6's. + */ +const TYPESCRIPT_6_API = '@typescript/typescript6'; + +/** + * Resolve the project's own `typescript`, trying each directory in order; + * where it has no JavaScript compiler API (TypeScript 7), the TypeScript 6 + * API package installed beside it. * @param {Array} dirs * @returns {{ts: any, version: string} | {ts: null, version: string} | null} * `ts: null` when a TypeScript is installed but has no `createProgram` @@ -43,20 +51,29 @@ export function resolveProjectTypeScript(dirs) { unusable ??= installed(); } catch { // Not installed from here, or installed without an entry this CLI can - // load; try the next directory. + // load. unusable ??= installed(); } + try { + const ts = require(TYPESCRIPT_6_API); + if (ts && typeof ts.createProgram === 'function') { + return {ts, version: String(ts.version)}; + } + } catch { + // Not installed from here; try the next directory. + } } return unusable ? {ts: null, version: unusable} : null; } /** - * What to do when the project's TypeScript is missing or cannot be used: a - * version every check here reads, 5.0 (the oldest that checks a JavaScript - * doc) through 6 (the newest with a JavaScript compiler API). + * What to do when the project's TypeScript is missing or cannot be used: + * beside TypeScript 7, the TypeScript 6 API package it ships for tools; + * otherwise a version every check here reads, 5.0 (the oldest that checks a + * JavaScript doc) through 6. */ export const TYPESCRIPT_FIX = - 'Install TypeScript 5.0 through 6 (`npm install -D typescript@6`) and run this check again.'; + 'On TypeScript 7, add its TypeScript 6 API package (`npm install -D @typescript/typescript6`); otherwise install TypeScript 5.0 through 6 (`npm install -D typescript@6`). Then run this check again.'; /** * Why a check that needs the compiler did not run, when the TypeScript diff --git a/packages/cli/foundation/discovery/prop-drift.test.mjs b/packages/cli/foundation/discovery/prop-drift.test.mjs index bc98ddcdcd883..53a4e6caa6309 100644 --- a/packages/cli/foundation/discovery/prop-drift.test.mjs +++ b/packages/cli/foundation/discovery/prop-drift.test.mjs @@ -58,6 +58,37 @@ function withTypeScript() { ); } +/** + * TypeScript 7 (no JavaScript compiler API) with the TypeScript 6 API package + * beside it, which re-exports a real TypeScript. + */ +function withTypeScript7AndItsTypeScript6Api() { + const ts7 = path.join(pkg, 'node_modules', 'typescript'); + fs.mkdirSync(ts7, {recursive: true}); + fs.writeFileSync( + path.join(ts7, 'package.json'), + JSON.stringify({ + name: 'typescript', + version: '7.0.2', + exports: {'./package.json': './package.json'}, + }), + ); + const ts6 = path.join(pkg, 'node_modules', '@typescript', 'typescript6'); + fs.mkdirSync(ts6, {recursive: true}); + fs.writeFileSync( + path.join(ts6, 'package.json'), + JSON.stringify({ + name: '@typescript/typescript6', + version: '6.0.2', + main: 'index.js', + }), + ); + fs.writeFileSync( + path.join(ts6, 'index.js'), + `module.exports = require(${JSON.stringify(TYPESCRIPT_DIR)});\n`, + ); +} + /** * Write `components/.tsx` and its doc; returns its prop table, which * also carries the component record (name, docPath, sourcePath). @@ -177,7 +208,7 @@ describe('comparePropTables', () => { findings: [], typesCompared: 0, typesSkipped: - 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (`npm install -D typescript@6`) to judge them', + 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (`npm install -D typescript@6`), or on TypeScript 7 add `@typescript/typescript6`, to judge them', }); }); @@ -2180,6 +2211,23 @@ export const Hoc = withName((props: {label?: string}) => null);`; ]); }); + it("compares with TypeScript 7's TypeScript 6 API package when the project has one", async () => { + const badge = component( + 'Badge', + 'export function Badge(props: {label: string}) { return null; }\n', + {props: [prop('label', 'number')]}, + ); + withTypeScript7AndItsTypeScript6Api(); + + const report = await compare([badge]); + + expect(report.status).toBe('checked'); + // The comparison ran: the documented `number` is judged against `string`. + expect( + report.findings.map(f => `${f.component}.${f.prop} ${f.kind}`), + ).toContain('Badge.label type_mismatch'); + }); + it('skips with a reason, and compares nothing, without a TypeScript it can use', async () => { const badge = component( 'Badge', @@ -2211,7 +2259,7 @@ export const Hoc = withName((props: {label?: string}) => null);`; ); const report = await compare([badge]); expect(report.reason).toContain('TypeScript 7.0.0'); - expect(report.reason).toContain('npm install -D typescript@6'); + expect(report.reason).toContain('npm install -D @typescript/typescript6'); expect(report).toEqual( skipped( unusableTypeScriptReason( diff --git a/packages/cli/foundation/discovery/type-text.mjs b/packages/cli/foundation/discovery/type-text.mjs index 6facbee51bc93..9ddd82134d242 100644 --- a/packages/cli/foundation/discovery/type-text.mjs +++ b/packages/cli/foundation/discovery/type-text.mjs @@ -46,7 +46,7 @@ export function typeTextTooOld(ts) { const [major, minor] = String(ts.version).split('.').map(Number); return major > 4 || (major === 4 && minor >= 8) ? null - : `TypeScript ${ts.version} is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (\`npm install -D typescript@6\`) to judge them`; + : `TypeScript ${ts.version} is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (\`npm install -D typescript@6\`), or on TypeScript 7 add \`@typescript/typescript6\`, to judge them`; } /** How tightly a printed type binds: a slot needs at least its level. */ diff --git a/packages/cli/foundation/discovery/type-text.test.mjs b/packages/cli/foundation/discovery/type-text.test.mjs index 96dd7665a3d27..76765f0eee4d4 100644 --- a/packages/cli/foundation/discovery/type-text.test.mjs +++ b/packages/cli/foundation/discovery/type-text.test.mjs @@ -176,7 +176,7 @@ describe('normalizeTypeText', () => { 'string[]', ]); expect(typeTextTooOld({version: '4.7.4'})).toBe( - 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (`npm install -D typescript@6`) to judge them', + 'TypeScript 4.7.4 is older than 4.8, the oldest the type comparison reads; install TypeScript 5.0 through 6 (`npm install -D typescript@6`), or on TypeScript 7 add `@typescript/typescript6`, to judge them', ); for (const version of ['4.8.4', '5.0.4', '6.0.3', ts.version]) { expect(typeTextTooOld({version})).toBeNull(); From 1acab46c987b7e348b97400a4019a5ca6569d1c0 Mon Sep 17 00:00:00 2001 From: Joey Farina Date: Mon, 19 Oct 2026 23:54:52 -0700 Subject: [PATCH 3/4] test(cli): list the TypeScript 6 API package load in the doc-loads inventory --- packages/cli/foundation/discovery/project-typescript.mjs | 7 +++++-- packages/cli/foundation/doc-compiler/doc-loads.test.mjs | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/cli/foundation/discovery/project-typescript.mjs b/packages/cli/foundation/discovery/project-typescript.mjs index 936707c44d49f..d7bbf04d835fc 100644 --- a/packages/cli/foundation/discovery/project-typescript.mjs +++ b/packages/cli/foundation/discovery/project-typescript.mjs @@ -15,8 +15,9 @@ import * as path from 'node:path'; import {createRequire} from 'node:module'; /** - * TypeScript 7 has no JavaScript compiler API; the package TypeScript ships - * beside it for tools that need one carries TypeScript 6's. + * The package TypeScript publishes beside TypeScript 7 for tools that need a + * JavaScript compiler API: TypeScript 6's. Kept in a constant so the type + * check does not treat the load as a static import of an uninstalled package. */ const TYPESCRIPT_6_API = '@typescript/typescript6'; @@ -54,6 +55,8 @@ export function resolveProjectTypeScript(dirs) { // load. unusable ??= installed(); } + // TypeScript 7 has no JavaScript compiler API; the package TypeScript + // ships beside it for tools that need one carries TypeScript 6's. try { const ts = require(TYPESCRIPT_6_API); if (ts && typeof ts.createProgram === 'function') { diff --git a/packages/cli/foundation/doc-compiler/doc-loads.test.mjs b/packages/cli/foundation/doc-compiler/doc-loads.test.mjs index 630f093b030e5..560a0ff3bb14a 100644 --- a/packages/cli/foundation/doc-compiler/doc-loads.test.mjs +++ b/packages/cli/foundation/doc-compiler/doc-loads.test.mjs @@ -257,8 +257,12 @@ const RUNNERS = { sites: ['importDocModule'], }, 'foundation/discovery/project-typescript.mjs': { - runs: "the project's own typescript, for the author checks that read source and docs with it, and its package.json for the version of one they cannot use", - sites: ['require(typescript)', 'require(typescript/package.json)'], + runs: "the project's own typescript, for the author checks that read source and docs with it, and its package.json for the version of one they cannot use; beside TypeScript 7, which has no compiler API, the TypeScript 6 API package @typescript/typescript6", + sites: [ + 'require()', + 'require(typescript)', + 'require(typescript/package.json)', + ], }, 'foundation/discovery/template-adapter.mjs': { runs: '@babel/parser, to find the fixture references and page-local bindings in template source; and a template doc that does not validate, to read the replacement it declares', From 4b00d50dbb6eed06474b351648c10cc13cd72047 Mon Sep 17 00:00:00 2001 From: Joey Farina Date: Tue, 20 Oct 2026 00:36:10 -0700 Subject: [PATCH 4/4] docs(cli): the checks read types with @typescript/typescript6 where typescript cannot be used The command docs, API docs, check guides and quick start now say the doc-type and prop checks use the project's `@typescript/typescript6` where its own `typescript` is missing or has no JavaScript compiler API (TypeScript 7). --- .changeset/typescript-fix-in-skip-reasons.md | 2 +- .../integrationComponentConflicts.doc.mjs | 12 +++++++++--- .../cli/api/integration/validateIntegration.doc.mjs | 4 +++- .../cli/assets/docs/tree/check-the-props.doc.mjs | 2 +- .../cli/assets/docs/tree/check-your-docs.doc.mjs | 2 +- packages/cli/assets/docs/tree/checks.doc.mjs | 2 +- packages/cli/assets/docs/tree/quick-start.doc.mjs | 2 +- .../commands/doctor-integration-components.doc.mjs | 4 +++- .../cli/commands/doctor-integration-validate.doc.mjs | 4 +++- 9 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.changeset/typescript-fix-in-skip-reasons.md b/.changeset/typescript-fix-in-skip-reasons.md index 2af115fd46677..3d995cc9a504f 100644 --- a/.changeset/typescript-fix-in-skip-reasons.md +++ b/.changeset/typescript-fix-in-skip-reasons.md @@ -2,5 +2,5 @@ '@astryxdesign/cli': patch --- -[feat] On TypeScript 7, which ships no JavaScript compiler API, the doc-type and prop checks in `integration check` and `doctor integration validate|components` use the TypeScript 6 API package TypeScript publishes for tools (`@typescript/typescript6`) when the project installs it beside TypeScript 7. When no usable TypeScript is found, the skip reason now ends with the remedy: add `@typescript/typescript6` on TypeScript 7, or install TypeScript 5.0 through 6 (`npm install -D typescript@6`). +[feat] The doc-type and prop checks in `integration check` and `doctor integration validate|components` read types with `@typescript/typescript6`, the TypeScript 6 API package TypeScript publishes for tools, when the project installs it and its own `typescript` is missing or has no JavaScript compiler API, as TypeScript 7 does. When no usable TypeScript is found, the skip reason now ends with the remedy: add `@typescript/typescript6` on TypeScript 7, or install TypeScript 5.0 through 6 (`npm install -D typescript@6`). @josephfarina diff --git a/packages/cli/api/integration/integrationComponentConflicts.doc.mjs b/packages/cli/api/integration/integrationComponentConflicts.doc.mjs index 7260289493642..dd89de00df4c7 100644 --- a/packages/cli/api/integration/integrationComponentConflicts.doc.mjs +++ b/packages/cli/api/integration/integrationComponentConflicts.doc.mjs @@ -18,7 +18,9 @@ export const doc = { 'replaces is checked too: its findings are issues, errors for a package that ' + 'declares the CLI range that turns replacement on and warnings otherwise. ' + "Each component doc's props are compared with the component's TypeScript props, " + - "read with the project's own `typescript`: a documented prop the code lacks, a " + + "read with the project's own `typescript` (or, where `typescript` is missing or has no " + + 'JavaScript compiler API, as TypeScript 7 does, the `@typescript/typescript6` it ' + + 'installs): a documented prop the code lacks, a ' + 'prop the package declares that the doc omits, a wrong `required`, or a type ' + 'that does not match. A type is judged only when it reads exactly: the same ' + 'text as the source, once whitespace, quotes and union order are set aside (exactly, if it contains `readonly`), ' + @@ -50,14 +52,18 @@ export const doc = { 'drift', ], params: [ - {name: 'pkg', type: 'string', description: 'Installed package; omit for the local package.'}, + { + name: 'pkg', + type: 'string', + description: 'Installed package; omit for the local package.', + }, {name: 'options.cwd', type: 'string', description: 'Resolution directory.'}, ], returns: [ { type: 'integration.component-conflicts', description: - '`validated` (false when no integration manifest was found, so nothing was inspected), integration identity, structural and replaces issues, and Core component-name conflicts with package-qualified commands. An active replacement that keeps the Core name it replaces is not a conflict. When the manifest loads and declares a components root, `propDrift` holds the props comparison: `status` (`checked` | `skipped`), `reason` when skipped, `typescript`, `components` compared, `typesCompared` (documented prop types judged), `typesSkipped` (why none was, with a TypeScript older than 4.8), `findings` ({component, prop, kind: `not_in_code` | `not_in_doc` | `required_mismatch` | `type_mismatch`, severity: `warning`, message, doc, source, docType?, codeType?, docRequired?, codeRequired?}), `unchecked` ({component, doc, reason}), `typesNotCompared` ({component, doc, reason, props}: every other documented prop type, grouped by why it was not judged: not a form the check judges exactly (a doc type that does not parse, or a prop a mapped type builds from keys the check cannot trace, included), a field the prop\'s variants give different types, a standard prop such as `className`, or a prop declared outside the package), and `propsNotChecked` when a mapped type builds props from keys the check cannot trace to the package ({component, doc, reason, props}: no warning is drawn for those props).', + "`validated` (false when no integration manifest was found, so nothing was inspected), integration identity, structural and replaces issues, and Core component-name conflicts with package-qualified commands. An active replacement that keeps the Core name it replaces is not a conflict. When the manifest loads and declares a components root, `propDrift` holds the props comparison: `status` (`checked` | `skipped`), `reason` when skipped, `typescript`, `components` compared, `typesCompared` (documented prop types judged), `typesSkipped` (why none was, with a TypeScript older than 4.8), `findings` ({component, prop, kind: `not_in_code` | `not_in_doc` | `required_mismatch` | `type_mismatch`, severity: `warning`, message, doc, source, docType?, codeType?, docRequired?, codeRequired?}), `unchecked` ({component, doc, reason}), `typesNotCompared` ({component, doc, reason, props}: every other documented prop type, grouped by why it was not judged: not a form the check judges exactly (a doc type that does not parse, or a prop a mapped type builds from keys the check cannot trace, included), a field the prop's variants give different types, a standard prop such as `className`, or a prop declared outside the package), and `propsNotChecked` when a mapped type builds props from keys the check cannot trace to the package ({component, doc, reason, props}: no warning is drawn for those props).", }, ], examples: [ diff --git a/packages/cli/api/integration/validateIntegration.doc.mjs b/packages/cli/api/integration/validateIntegration.doc.mjs index ed81df7a52e52..d991a29d7cf16 100644 --- a/packages/cli/api/integration/validateIntegration.doc.mjs +++ b/packages/cli/api/integration/validateIntegration.doc.mjs @@ -26,7 +26,9 @@ export const doc = { 'can stay exit-0 in a non-integration directory. It also checks every doc the ' + 'package ships under its roots (component, template, docs topic, theme, and translation, served or not) against ' + "the published authoring type for its kind, as `tsc` would, with the project's " + - 'own `typescript`: an unknown field, a missing required field, or a value outside ' + + 'own `typescript` (or, where `typescript` is missing or has no JavaScript compiler ' + + 'API, as TypeScript 7 does, the `@typescript/typescript6` it installs): an unknown ' + + 'field, a missing required field, or a value outside ' + "a field's type is a warning in `docTypes` naming the field and a fix. A test " + 'fixture under `__tests__` in the components or docs root is counted, not checked. ' + 'Without TypeScript, `docTypes` is `skipped` with a reason.', diff --git a/packages/cli/assets/docs/tree/check-the-props.doc.mjs b/packages/cli/assets/docs/tree/check-the-props.doc.mjs index 35fa7c4937417..a1eacd41fd271 100644 --- a/packages/cli/assets/docs/tree/check-the-props.doc.mjs +++ b/packages/cli/assets/docs/tree/check-the-props.doc.mjs @@ -58,7 +58,7 @@ docType: boolean style: 'unordered', items: [ "A doc's own `props` describe the export its `name` names. In a family doc, each entry describes its own export. A hook entry documents parameters and returns, so it is not compared.", - "It reads props with the `typescript` your package installs, and reads your package's `tsconfig.json` the way `tsc -p` does. Without TypeScript it prints `[skip] Props not compared` and the reason.", + "It reads props with the `typescript` your package installs, and reads your package's `tsconfig.json` the way `tsc -p` does. Where `typescript` is missing or has no JavaScript compiler API, as TypeScript 7 does, it uses `@typescript/typescript6`, the TypeScript 6 API package, if your package installs it. Without either it prints `[skip] Props not compared` and the reason.", 'Every finding is a warning, so the exit code stays 0. Read them before you publish. With `--json`, they are in `data.propDrift.findings`.', ], }, diff --git a/packages/cli/assets/docs/tree/check-your-docs.doc.mjs b/packages/cli/assets/docs/tree/check-your-docs.doc.mjs index 12214adbd6647..017f883536375 100644 --- a/packages/cli/assets/docs/tree/check-your-docs.doc.mjs +++ b/packages/cli/assets/docs/tree/check-your-docs.doc.mjs @@ -120,7 +120,7 @@ fix: Remove \`summary\`: SingleComponentDoc has no field \`summary\`.`, items: [ "The folder decides the type: `ComponentDoc` in your components root, `IntegrationTemplateDoc` in your templates root, `ThemeDoc` in your themes root, and in your docs root the type the doc's `type` names. Keep the `@type` comment above each doc's export too, so your editor shows the same problems as you type.", 'Every finding is a warning, so the exit code stays 0. With `--json`, they are in `data.docTypes.findings`.', - 'It uses the `typescript` your package installs. Without it, it prints `[skip] Docs not type-checked` and the reason.', + 'It uses the `typescript` your package installs. Where `typescript` is missing or has no JavaScript compiler API, as TypeScript 7 does, it uses `@typescript/typescript6`, the TypeScript 6 API package, if your package installs it. Without either, it prints `[skip] Docs not type-checked` and the reason.', 'Every field of every doc type is in `astryx docs authoring`.', ], }, diff --git a/packages/cli/assets/docs/tree/checks.doc.mjs b/packages/cli/assets/docs/tree/checks.doc.mjs index 0922e84b3d25c..d6f3792294951 100644 --- a/packages/cli/assets/docs/tree/checks.doc.mjs +++ b/packages/cli/assets/docs/tree/checks.doc.mjs @@ -32,7 +32,7 @@ export const docs = { '`npx astryx doctor integration validate`', 'The manifest loads, each root holds contributions the CLI can read, and each doc matches its authoring type', 'A declared root is missing (`missing_root`), a contribution does not load (`invalid_doc`, `invalid_component`, `invalid_theme`), or two templates in the package replace one Core id (`ambiguous_template_replacement`)', - "The manifest has a key this CLI does not know (`unknown_manifest_key`), or a doc field does not match the doc's authoring type: a field the type does not have, a missing required field, or a value outside the field's type. It checks docs with your installed `typescript`; without it, it prints `[skip] Docs not type-checked`. With no `astryx.integration.mjs` it prints a hint and exits 0", + "The manifest has a key this CLI does not know (`unknown_manifest_key`), or a doc field does not match the doc's authoring type: a field the type does not have, a missing required field, or a value outside the field's type. It checks docs with your installed `typescript`, or, where that is missing or has no JavaScript compiler API (TypeScript 7), with an installed `@typescript/typescript6`; without either, it prints `[skip] Docs not type-checked`. With no `astryx.integration.mjs` it prints a hint and exits 0", ], [ '`npx astryx doctor integration components`', diff --git a/packages/cli/assets/docs/tree/quick-start.doc.mjs b/packages/cli/assets/docs/tree/quick-start.doc.mjs index 457bdb2054987..3f61bb079d646 100644 --- a/packages/cli/assets/docs/tree/quick-start.doc.mjs +++ b/packages/cli/assets/docs/tree/quick-start.doc.mjs @@ -49,7 +49,7 @@ export const docs = { 'Start with `"exports": {}`: each component and template you add then writes the public import that `integration verify` resolves.', 'Run the CLI as `npx astryx`, which runs the `@astryxdesign/cli` you installed as a devDependency.', 'Component commands read Core, so they need `@astryxdesign/core` installed.', - "The checks compare your docs with your code through TypeScript's JavaScript compiler API, so they need `typescript` 5.0 through 6 installed. TypeScript 7 does not ship that API, and with it the checks skip those comparisons.", + "The checks compare your docs with your code through TypeScript's JavaScript compiler API, which `typescript` 5.0 through 6 has. TypeScript 7 does not ship that API: on TypeScript 7, also install `@typescript/typescript6`, its TypeScript 6 API package, and the checks use it.", ], }, { diff --git a/packages/cli/clients/cli/commands/doctor-integration-components.doc.mjs b/packages/cli/clients/cli/commands/doctor-integration-components.doc.mjs index 2433912e81d24..e984a5b9c40ff 100644 --- a/packages/cli/clients/cli/commands/doctor-integration-components.doc.mjs +++ b/packages/cli/clients/cli/commands/doctor-integration-components.doc.mjs @@ -20,7 +20,9 @@ export const doc = { 'Core component is an error. In a package without that range they are warnings, ' + 'with a warning for each such component, naming the range to declare. ' + "Then it compares each component doc's props with the component's TypeScript " + - "props, using the project's own `typescript`, and warns about a documented prop " + + "props, using the project's own `typescript` (or, where `typescript` is missing or " + + 'has no JavaScript compiler API, as TypeScript 7 does, the `@typescript/typescript6` ' + + 'it installs), and warns about a documented prop ' + 'the code lacks, a prop the package declares that the doc omits, a wrong ' + '`required`, and a type that does not match where both sides read exactly ' + '(it lists every other documented type as not compared, with the reason). ' + diff --git a/packages/cli/clients/cli/commands/doctor-integration-validate.doc.mjs b/packages/cli/clients/cli/commands/doctor-integration-validate.doc.mjs index 3816c1e533fa9..27a3d4b4680f1 100644 --- a/packages/cli/clients/cli/commands/doctor-integration-validate.doc.mjs +++ b/packages/cli/clients/cli/commands/doctor-integration-validate.doc.mjs @@ -16,7 +16,9 @@ export const doc = { 'installed package resolved by name. It schema-checks the manifest, verifies ' + 'every declared contribution root, and reports every finding. Safe as a CI gate. ' + 'It then checks every doc the package ships under its roots against the ' + - "published authoring type for its kind, with the project's own `typescript`, and " + + "published authoring type for its kind, with the project's own `typescript` (or, " + + 'where `typescript` is missing or has no JavaScript compiler API, as TypeScript 7 ' + + 'does, the `@typescript/typescript6` it installs), and ' + 'warns about each field that does not match, naming the field and a fix. A doc ' + 'it cannot fully check is listed with the reason, and test fixtures under ' + '`__tests__` in the components or docs root are counted, not checked. Without ' +