tempest-react-sdk 0.29.0 → 0.29.1
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/bin/lib/alias/index.mjs +9 -3
- package/bin/lib/alias/tsconfig.mjs +48 -5
- package/bin/lib/alias/typescript.mjs +72 -2
- package/bin/lib/alias/typescript.test.mjs +120 -0
- package/bin/lib/css/extract.mjs +2 -3
- package/bin/lib/doctor/doctor.e2e.test.mjs +44 -0
- package/bin/tempest.mjs +17 -2
- package/package.json +1 -1
package/bin/lib/alias/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { collectFiles } from "./collect.mjs";
|
|
|
7
7
|
import { discoverAlias } from "./discover.mjs";
|
|
8
8
|
import { rewriteCss } from "./rewrite-css.mjs";
|
|
9
9
|
import { rewriteSource } from "./rewrite.mjs";
|
|
10
|
-
import { loadTypeScript } from "./typescript.mjs";
|
|
10
|
+
import { loadTypeScript, typeScriptUnavailableReason } from "./typescript.mjs";
|
|
11
11
|
|
|
12
12
|
export { collectFiles } from "./collect.mjs";
|
|
13
13
|
export { discoverAlias } from "./discover.mjs";
|
|
@@ -15,7 +15,7 @@ export { rewriteCss } from "./rewrite-css.mjs";
|
|
|
15
15
|
export { rewriteSource } from "./rewrite.mjs";
|
|
16
16
|
export { isInsideBase, toAlias } from "./specifier.mjs";
|
|
17
17
|
export { readTsconfig } from "./tsconfig.mjs";
|
|
18
|
-
export { loadTypeScript } from "./typescript.mjs";
|
|
18
|
+
export { describeTypeScript, loadTypeScript, typeScriptUnavailableReason } from "./typescript.mjs";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Convert relative imports that climb out of their directory into alias imports.
|
|
@@ -42,7 +42,13 @@ export function aliasImports({ root, targets, dryRun = false }) {
|
|
|
42
42
|
const empty = { files: [], total: 0, errors: [] };
|
|
43
43
|
|
|
44
44
|
const ts = loadTypeScript(root);
|
|
45
|
-
if (!ts)
|
|
45
|
+
if (!ts) {
|
|
46
|
+
return {
|
|
47
|
+
status: "no-typescript",
|
|
48
|
+
reason: typeScriptUnavailableReason(root),
|
|
49
|
+
...empty,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
46
52
|
|
|
47
53
|
const alias = discoverAlias({ root, ts });
|
|
48
54
|
if (!alias) return { status: "no-alias", ...empty };
|
|
@@ -6,19 +6,62 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
6
6
|
|
|
7
7
|
const MAX_EXTENDS_DEPTH = 16;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Strip the JSONC a tsconfig is allowed to contain: `//` and block comments, and
|
|
11
|
+
* a trailing comma before `}` or `]`.
|
|
12
|
+
*
|
|
13
|
+
* Needed because `ts.readConfigFile` is not always available — TypeScript 7 does
|
|
14
|
+
* not ship the classic API — and a plain `JSON.parse` chokes on the comments
|
|
15
|
+
* every Vite-generated tsconfig has. Losing the whole config to a comment would
|
|
16
|
+
* silently drop the `strict`/`jsx`/`paths` checks.
|
|
17
|
+
*
|
|
18
|
+
* Comment markers inside a string are left alone, which matters: `"paths"` and
|
|
19
|
+
* `"extends"` values legitimately hold `//` (a URL, a Windows share).
|
|
20
|
+
*
|
|
21
|
+
* @param {string} text
|
|
22
|
+
* @returns {string} Plain JSON.
|
|
23
|
+
*/
|
|
24
|
+
export function stripJsonc(text) {
|
|
25
|
+
let out = "";
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < text.length) {
|
|
28
|
+
const ch = text[i];
|
|
29
|
+
if (ch === '"') {
|
|
30
|
+
let j = i + 1;
|
|
31
|
+
while (j < text.length && text[j] !== '"') j += text[j] === "\\" ? 2 : 1;
|
|
32
|
+
out += text.slice(i, Math.min(j + 1, text.length));
|
|
33
|
+
i = j + 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
37
|
+
const end = text.indexOf("\n", i);
|
|
38
|
+
i = end < 0 ? text.length : end;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
42
|
+
const end = text.indexOf("*/", i + 2);
|
|
43
|
+
i = end < 0 ? text.length : end + 2;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
out += ch;
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
50
|
+
}
|
|
51
|
+
|
|
9
52
|
/**
|
|
10
53
|
* Parse one tsconfig file into its raw JSON object.
|
|
11
54
|
*
|
|
12
|
-
* Prefers `ts.readConfigFile`, which accepts the
|
|
13
|
-
*
|
|
14
|
-
* TypeScript
|
|
55
|
+
* Prefers `ts.readConfigFile`, which accepts the exact dialect `tsc` accepts.
|
|
56
|
+
* Falls back to a JSONC-tolerant parse so a project without the classic
|
|
57
|
+
* TypeScript API still gets its config read instead of nothing.
|
|
15
58
|
*
|
|
16
59
|
* @param {string} path - Absolute path to a tsconfig file.
|
|
17
60
|
* @param {object | null} ts - The `typescript` module, or `null`.
|
|
18
61
|
* @returns {object | null} The raw config object, or `null` when unreadable.
|
|
19
62
|
*/
|
|
20
63
|
function parseConfigFile(path, ts) {
|
|
21
|
-
if (ts) {
|
|
64
|
+
if (ts && typeof ts.readConfigFile === "function") {
|
|
22
65
|
const { config, error } = ts.readConfigFile(path, (p) => {
|
|
23
66
|
try {
|
|
24
67
|
return readFileSync(p, "utf8");
|
|
@@ -29,7 +72,7 @@ function parseConfigFile(path, ts) {
|
|
|
29
72
|
return error || !config ? null : config;
|
|
30
73
|
}
|
|
31
74
|
try {
|
|
32
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
75
|
+
return JSON.parse(stripJsonc(readFileSync(path, "utf8")));
|
|
33
76
|
} catch {
|
|
34
77
|
return null;
|
|
35
78
|
}
|
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The classic-API members every codemod in this CLI needs.
|
|
8
|
+
*
|
|
9
|
+
* TypeScript 7 is the native port: its package still installs under the name
|
|
10
|
+
* `typescript`, but the `.` export is a version stub — the JS API moved behind
|
|
11
|
+
* `typescript/unstable/*` with a different shape. So "the module resolved" no
|
|
12
|
+
* longer implies "the API is there", and calling into it blew up with
|
|
13
|
+
* `ts.readConfigFile is not a function` — a stack trace out of `tempest doctor`,
|
|
14
|
+
* which is the command people run first.
|
|
15
|
+
*/
|
|
16
|
+
const REQUIRED_API = ["readConfigFile", "createSourceFile", "forEachChild"];
|
|
17
|
+
|
|
18
|
+
/** True when the resolved module exposes the classic compiler API. */
|
|
19
|
+
function hasClassicApi(module) {
|
|
20
|
+
return Boolean(module) && REQUIRED_API.every((name) => typeof module[name] === "function");
|
|
21
|
+
}
|
|
22
|
+
|
|
6
23
|
/**
|
|
7
24
|
* Load `typescript` from the project's `node_modules`.
|
|
8
25
|
*
|
|
@@ -11,13 +28,66 @@ import { join } from "node:path";
|
|
|
11
28
|
* compiler version than the one that type-checks it would let syntax the app
|
|
12
29
|
* accepts fail here.
|
|
13
30
|
*
|
|
31
|
+
* Returns `null` both when TypeScript is absent and when the install does not
|
|
32
|
+
* expose the classic API, because callers treat the two the same way — report it
|
|
33
|
+
* and leave the source untouched. Use {@link describeTypeScript} to say which of
|
|
34
|
+
* the two happened.
|
|
35
|
+
*
|
|
14
36
|
* @param {string} root - Project root, the directory holding `node_modules`.
|
|
15
|
-
* @returns {object | null} The `typescript` module, or `null
|
|
37
|
+
* @returns {object | null} The `typescript` module, or `null`.
|
|
16
38
|
*/
|
|
17
39
|
export function loadTypeScript(root) {
|
|
18
40
|
try {
|
|
19
|
-
|
|
41
|
+
const module = createRequire(join(root, "package.json"))("typescript");
|
|
42
|
+
return hasClassicApi(module) ? module : null;
|
|
20
43
|
} catch {
|
|
21
44
|
return null;
|
|
22
45
|
}
|
|
23
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* What the project's TypeScript install is, so a message can say the truth
|
|
50
|
+
* instead of "not installed" about a package that is right there.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} root - Project root.
|
|
53
|
+
* @returns {{ status: "ok" | "missing" | "api-unavailable", version: string | null }}
|
|
54
|
+
*/
|
|
55
|
+
export function describeTypeScript(root) {
|
|
56
|
+
let resolve_;
|
|
57
|
+
try {
|
|
58
|
+
resolve_ = createRequire(join(root, "package.json"));
|
|
59
|
+
} catch {
|
|
60
|
+
return { status: "missing", version: null };
|
|
61
|
+
}
|
|
62
|
+
let version = null;
|
|
63
|
+
try {
|
|
64
|
+
version = resolve_("typescript/package.json")?.version ?? null;
|
|
65
|
+
} catch {
|
|
66
|
+
return { status: "missing", version: null };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return {
|
|
70
|
+
status: hasClassicApi(resolve_("typescript")) ? "ok" : "api-unavailable",
|
|
71
|
+
version,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return { status: "api-unavailable", version };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One line explaining why a codemod cannot run, or `null` when it can.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} root - Project root.
|
|
82
|
+
* @returns {string | null}
|
|
83
|
+
*/
|
|
84
|
+
export function typeScriptUnavailableReason(root) {
|
|
85
|
+
const { status, version } = describeTypeScript(root);
|
|
86
|
+
if (status === "ok") return null;
|
|
87
|
+
if (status === "missing") return "typescript não instalado (npm i -D typescript)";
|
|
88
|
+
return (
|
|
89
|
+
`typescript ${version ?? "?"} não expõe a API clássica do compilador — o 7 publica só ` +
|
|
90
|
+
"`typescript/unstable/*`, com outra forma. Pra usar os codemods, tenha o TypeScript 6 " +
|
|
91
|
+
"instalado; o resto do comando segue normal"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { readTsconfig, stripJsonc } from "./tsconfig.mjs";
|
|
7
|
+
import { describeTypeScript, loadTypeScript, typeScriptUnavailableReason } from "./typescript.mjs";
|
|
8
|
+
|
|
9
|
+
let root;
|
|
10
|
+
|
|
11
|
+
/** Write a file under the fixture, creating parent directories. */
|
|
12
|
+
function write(rel, text) {
|
|
13
|
+
const full = join(root, rel);
|
|
14
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
15
|
+
writeFileSync(full, text);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fake an installed `typescript` whose main module exports `exports`.
|
|
20
|
+
*
|
|
21
|
+
* TypeScript 7 is exactly this shape: the package resolves, the version is there,
|
|
22
|
+
* and the classic API is not — it moved to `typescript/unstable/*`.
|
|
23
|
+
*/
|
|
24
|
+
function fakeTypeScript(version, exports) {
|
|
25
|
+
write(
|
|
26
|
+
"node_modules/typescript/package.json",
|
|
27
|
+
JSON.stringify({ name: "typescript", version, main: "index.js" }),
|
|
28
|
+
);
|
|
29
|
+
write("node_modules/typescript/index.js", `module.exports = ${exports};`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
root = mkdtempSync(join(tmpdir(), "tempest-ts-"));
|
|
34
|
+
write("package.json", JSON.stringify({ name: "fixture" }));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
rmSync(root, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("loadTypeScript", () => {
|
|
42
|
+
it("returns null when typescript is not installed", () => {
|
|
43
|
+
expect(loadTypeScript(root)).toBeNull();
|
|
44
|
+
expect(describeTypeScript(root)).toEqual({ status: "missing", version: null });
|
|
45
|
+
expect(typeScriptUnavailableReason(root)).toContain("não instalado");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns null for an install without the classic compiler API", () => {
|
|
49
|
+
fakeTypeScript("7.0.2", '{ version: "7.0.2", versionMajorMinor: "7.0" }');
|
|
50
|
+
expect(loadTypeScript(root)).toBeNull();
|
|
51
|
+
expect(describeTypeScript(root)).toEqual({ status: "api-unavailable", version: "7.0.2" });
|
|
52
|
+
const reason = typeScriptUnavailableReason(root);
|
|
53
|
+
expect(reason).toContain("7.0.2");
|
|
54
|
+
expect(reason).toContain("unstable");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns the module when the classic API is there", () => {
|
|
58
|
+
fakeTypeScript(
|
|
59
|
+
"6.0.3",
|
|
60
|
+
"{ readConfigFile: () => ({}), createSourceFile: () => ({}), forEachChild: () => {} }",
|
|
61
|
+
);
|
|
62
|
+
expect(loadTypeScript(root)).not.toBeNull();
|
|
63
|
+
expect(describeTypeScript(root)).toEqual({ status: "ok", version: "6.0.3" });
|
|
64
|
+
expect(typeScriptUnavailableReason(root)).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("treats a partial API as unavailable rather than half-usable", () => {
|
|
68
|
+
fakeTypeScript("7.1.0", "{ createSourceFile: () => ({}) }");
|
|
69
|
+
expect(loadTypeScript(root)).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("stripJsonc", () => {
|
|
74
|
+
it("removes line and block comments", () => {
|
|
75
|
+
expect(stripJsonc('{ // hi\n "a": 1 /* there */ }')).toBe('{ \n "a": 1 }');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("removes a trailing comma before a close", () => {
|
|
79
|
+
expect(JSON.parse(stripJsonc('{ "a": [1, 2,], }'))).toEqual({ a: [1, 2] });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("leaves comment markers inside strings alone", () => {
|
|
83
|
+
expect(JSON.parse(stripJsonc('{ "url": "https://x.dev/a", "p": "a/*b" }'))).toEqual({
|
|
84
|
+
url: "https://x.dev/a",
|
|
85
|
+
p: "a/*b",
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("survives an unterminated comment instead of throwing", () => {
|
|
90
|
+
expect(() => stripJsonc('{ "a": 1 } /* forever')).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("readTsconfig without the compiler API", () => {
|
|
95
|
+
it("still reads a tsconfig that has comments and a trailing comma", () => {
|
|
96
|
+
write(
|
|
97
|
+
"tsconfig.json",
|
|
98
|
+
'{\n // the app\n "compilerOptions": {\n "strict": true,\n "paths": { "@/*": ["./src/*"] },\n },\n}\n',
|
|
99
|
+
);
|
|
100
|
+
const config = readTsconfig({ root, ts: null });
|
|
101
|
+
expect(config.compilerOptions.strict).toBe(true);
|
|
102
|
+
expect(config.compilerOptions.paths["@/*"]).toEqual(["./src/*"]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("follows extends with the fallback parser", () => {
|
|
106
|
+
write("base.json", '{ "compilerOptions": { "jsx": "react-jsx" } } // base\n');
|
|
107
|
+
write(
|
|
108
|
+
"tsconfig.json",
|
|
109
|
+
'{ "extends": "./base.json", "compilerOptions": { "strict": true } }',
|
|
110
|
+
);
|
|
111
|
+
const config = readTsconfig({ root, ts: null });
|
|
112
|
+
expect(config.compilerOptions).toMatchObject({ jsx: "react-jsx", strict: true });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("ignores a `ts` that cannot read a config file", () => {
|
|
116
|
+
write("tsconfig.json", '{ "compilerOptions": { "strict": true } }');
|
|
117
|
+
const config = readTsconfig({ root, ts: { version: "7.0.2" } });
|
|
118
|
+
expect(config.compilerOptions.strict).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
package/bin/lib/css/extract.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
25
25
|
import { join, relative } from "node:path";
|
|
26
26
|
|
|
27
27
|
import { discoverAlias } from "../alias/discover.mjs";
|
|
28
|
-
import { loadTypeScript } from "../alias/typescript.mjs";
|
|
28
|
+
import { loadTypeScript, typeScriptUnavailableReason } from "../alias/typescript.mjs";
|
|
29
29
|
import { collectSources, indexClassUses, resolveSpecifier } from "./references.mjs";
|
|
30
30
|
import { parseCss } from "./parse.mjs";
|
|
31
31
|
import { declSignature } from "./semantic.mjs";
|
|
@@ -192,8 +192,7 @@ export function planExtraction({ analysis, root, target, prefix = "u-" }) {
|
|
|
192
192
|
if (!ts) {
|
|
193
193
|
return {
|
|
194
194
|
status: "no-typescript",
|
|
195
|
-
message:
|
|
196
|
-
"typescript não instalado — a reescrita do TSX precisa do compilador do projeto",
|
|
195
|
+
message: `a reescrita do TSX precisa do compilador do projeto: ${typeScriptUnavailableReason(root)}`,
|
|
197
196
|
groups: [],
|
|
198
197
|
refusals: [],
|
|
199
198
|
};
|
|
@@ -155,6 +155,50 @@ describe("tempest doctor — generic findings still surface", () => {
|
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
describe("tempest doctor — a project on TypeScript 7", () => {
|
|
159
|
+
/**
|
|
160
|
+
* TypeScript 7 is the native port: same package name, and no classic compiler
|
|
161
|
+
* API — it moved to `typescript/unstable/*`. Before this was detected, `doctor`
|
|
162
|
+
* died with `ts.readConfigFile is not a function` on any such project, which is
|
|
163
|
+
* the worst possible failure for the command people run first.
|
|
164
|
+
*/
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
thirdPartyApp();
|
|
167
|
+
write("node_modules/typescript/package.json", {
|
|
168
|
+
name: "typescript",
|
|
169
|
+
version: "7.0.2",
|
|
170
|
+
main: "index.js",
|
|
171
|
+
});
|
|
172
|
+
write(
|
|
173
|
+
"node_modules/typescript/index.js",
|
|
174
|
+
'module.exports = { version: "7.0.2", versionMajorMinor: "7.0" };',
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("does not crash, and still reports the tsconfig checks", () => {
|
|
179
|
+
const { out, code } = doctor();
|
|
180
|
+
expect(out).not.toContain("is not a function");
|
|
181
|
+
expect(out).toContain("moduleResolution: bundler");
|
|
182
|
+
expect(code).toBe(0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("says the codemods are unavailable, and why", () => {
|
|
186
|
+
const { out } = doctor();
|
|
187
|
+
expect(out).toContain("no classic compiler API");
|
|
188
|
+
expect(out).toContain("every other pass runs");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("reads a tsconfig with comments through the fallback parser", () => {
|
|
192
|
+
writeFileSync(
|
|
193
|
+
join(root, "tsconfig.json"),
|
|
194
|
+
'{\n // vite default\n "compilerOptions": { "strict": true, "jsx": "react-jsx", "moduleResolution": "bundler" },\n}\n',
|
|
195
|
+
);
|
|
196
|
+
const { out } = doctor();
|
|
197
|
+
expect(out).toContain("strict mode on");
|
|
198
|
+
expect(out).not.toContain("strict mode off");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
158
202
|
describe("tempest doctor — stylesheets", () => {
|
|
159
203
|
beforeEach(thirdPartyApp);
|
|
160
204
|
|
package/bin/tempest.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
12
12
|
import { dirname, join, resolve } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { aliasImports } from "./lib/alias/index.mjs";
|
|
15
|
-
import { loadTypeScript } from "./lib/alias/typescript.mjs";
|
|
15
|
+
import { describeTypeScript, loadTypeScript } from "./lib/alias/typescript.mjs";
|
|
16
16
|
import { readTsconfig } from "./lib/alias/tsconfig.mjs";
|
|
17
17
|
import { analyzeCss, applyCssFixes, applyExtraction, planExtraction } from "./lib/css/index.mjs";
|
|
18
18
|
import { checkLucide } from "./lib/doctor/lucide.mjs";
|
|
@@ -535,6 +535,21 @@ function doctor() {
|
|
|
535
535
|
const tsc = readTsconfig({ root: ROOT, ts: loadTypeScript(ROOT) });
|
|
536
536
|
if (tsc) {
|
|
537
537
|
checks.push(["section", "TypeScript"]);
|
|
538
|
+
/*
|
|
539
|
+
* TypeScript 7 installs under the same package name and does not ship the
|
|
540
|
+
* classic compiler API — it lives behind `typescript/unstable/*` with a
|
|
541
|
+
* different shape. The tsconfig checks below keep working (they fall back to
|
|
542
|
+
* a JSONC-tolerant parse), but the codemods cannot run, and saying so here is
|
|
543
|
+
* better than each pass reporting it as if TypeScript were missing.
|
|
544
|
+
*/
|
|
545
|
+
const tsInstall = describeTypeScript(ROOT);
|
|
546
|
+
if (tsInstall.status === "api-unavailable") {
|
|
547
|
+
checks.push([
|
|
548
|
+
"info",
|
|
549
|
+
`typescript ${tsInstall.version} has no classic compiler API`,
|
|
550
|
+
"`tempest fix` skips the alias and --extract-css codemods — they need the TypeScript 6 API; every other pass runs",
|
|
551
|
+
]);
|
|
552
|
+
}
|
|
538
553
|
const co = tsc.compilerOptions;
|
|
539
554
|
// The `@/*` alias is the SDK's convention, not a health property: a project
|
|
540
555
|
// that has not adopted the SDK is not wrong for lacking it.
|
|
@@ -855,7 +870,7 @@ function parseFixArgs(args) {
|
|
|
855
870
|
function reportAlias(result, dryRun) {
|
|
856
871
|
if (result.status === "no-typescript") {
|
|
857
872
|
console.log(
|
|
858
|
-
`${c.yellow}!
|
|
873
|
+
`${c.yellow}! alias pass skipped${c.reset} ${c.dim}— ${result.reason ?? "typescript não instalado"}${c.reset}`,
|
|
859
874
|
);
|
|
860
875
|
return;
|
|
861
876
|
}
|