Skip to content

Commit 76f80a5

Browse files
committed
fix
1 parent 4a08e64 commit 76f80a5

File tree

5 files changed

+112
-11
lines changed

5 files changed

+112
-11
lines changed

packages/react-doctor/src/cli.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
33
import os from "node:os";
44
import path from "node:path";
55
import { Command } from "commander";
6-
import { SEPARATOR_LENGTH_CHARS } from "./constants.js";
6+
import { OPEN_BASE_URL, SEPARATOR_LENGTH_CHARS } from "./constants.js";
77
import { scan } from "./scan.js";
88
import type { DiffInfo, ScanOptions } from "./types.js";
99
import { copyToClipboard } from "./utils/copy-to-clipboard.js";
@@ -27,6 +27,7 @@ interface CliFlags {
2727
fix: boolean;
2828
prompt: boolean;
2929
yes: boolean;
30+
offline: boolean;
3031
project?: string;
3132
diff?: boolean | string;
3233
}
@@ -77,6 +78,7 @@ const program = new Command()
7778
.option("-y, --yes", "skip prompts, scan all workspace projects")
7879
.option("--project <name>", "select workspace project (comma-separated for multiple)")
7980
.option("--diff [base]", "scan only files changed vs base branch")
81+
.option("--offline", "skip telemetry (anonymous, not stored, only used to calculate score)")
8082
.option("--fix", "open Ami to auto-fix all issues")
8183
.option("--prompt", "copy latest scan output to clipboard")
8284
.action(async (directory: string, flags: CliFlags) => {
@@ -108,6 +110,7 @@ const program = new Command()
108110
flags.prompt ||
109111
(isCliOverride("verbose") ? Boolean(flags.verbose) : (userConfig?.verbose ?? false)),
110112
scoreOnly: isScoreOnly,
113+
offline: flags.offline,
111114
};
112115

113116
const isAutomatedEnvironment = [
@@ -254,15 +257,25 @@ const openUrl = (url: string): void => {
254257
execSync(openCommand, { stdio: "ignore" });
255258
};
256259

257-
const buildDeeplink = (directory: string): string => {
258-
const encodedDirectory = encodeURIComponent(path.resolve(directory));
259-
const encodedPrompt = encodeURIComponent(DEEPLINK_FIX_PROMPT);
260-
return `ami://open-project?cwd=${encodedDirectory}&prompt=${encodedPrompt}&mode=agent&autoSubmit=true`;
260+
const buildDeeplinkParams = (directory: string): URLSearchParams => {
261+
const params = new URLSearchParams();
262+
params.set("cwd", path.resolve(directory));
263+
params.set("prompt", DEEPLINK_FIX_PROMPT);
264+
params.set("mode", "agent");
265+
params.set("autoSubmit", "true");
266+
return params;
261267
};
262268

269+
const buildDeeplink = (directory: string): string =>
270+
`ami://open-project?${buildDeeplinkParams(directory).toString()}`;
271+
272+
const buildWebDeeplink = (directory: string): string =>
273+
`${OPEN_BASE_URL}?${buildDeeplinkParams(directory).toString()}`;
274+
263275
const openAmiToFix = (directory: string): void => {
264276
const isInstalled = isAmiInstalled();
265277
const deeplink = buildDeeplink(directory);
278+
const webDeeplink = buildWebDeeplink(directory);
266279

267280
if (!isInstalled) {
268281
if (process.platform === "darwin") {
@@ -274,7 +287,7 @@ const openAmiToFix = (directory: string): void => {
274287
}
275288
logger.break();
276289
logger.dim("Once Ami is running, open this link to start fixing:");
277-
logger.info(deeplink);
290+
logger.info(webDeeplink);
278291
return;
279292
}
280293

@@ -286,7 +299,7 @@ const openAmiToFix = (directory: string): void => {
286299
} catch {
287300
logger.break();
288301
logger.dim("Could not open Ami automatically. Open this URL manually:");
289-
logger.info(deeplink);
302+
logger.info(webDeeplink);
290303
}
291304
};
292305

packages/react-doctor/src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@ export const SCORE_API_URL = "https://www.react.doctor/api/score";
2424

2525
export const SHARE_BASE_URL = "https://www.react.doctor/share";
2626

27+
export const OPEN_BASE_URL = "https://www.react.doctor/open";
28+
2729
export const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
2830

2931
export const OFFLINE_MESSAGE =
3032
"You are offline, could not calculate score. Reconnect to calculate.";
3133

34+
export const OFFLINE_FLAG_MESSAGE =
35+
"Score not calculated. Remove --offline to calculate score.";
36+
3237
export const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];

packages/react-doctor/src/scan.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { performance } from "node:perf_hooks";
66
import {
77
JSX_FILE_PATTERN,
88
MILLISECONDS_PER_SECOND,
9+
OFFLINE_FLAG_MESSAGE,
910
OFFLINE_MESSAGE,
1011
PERFECT_SCORE,
1112
SCORE_BAR_WIDTH_CHARS,
@@ -264,6 +265,7 @@ const printSummary = (
264265
scoreResult: ScoreResult | null,
265266
projectName: string,
266267
totalSourceFileCount: number,
268+
noScoreMessage: string,
267269
): void => {
268270
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
269271
const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
@@ -326,7 +328,7 @@ const printSummary = (
326328
),
327329
);
328330
summaryFramedLines.push(createFramedLine(""));
329-
summaryFramedLines.push(createFramedLine(OFFLINE_MESSAGE, highlighter.dim(OFFLINE_MESSAGE)));
331+
summaryFramedLines.push(createFramedLine(noScoreMessage, highlighter.dim(noScoreMessage)));
330332
summaryFramedLines.push(createFramedLine(""));
331333
}
332334

@@ -358,6 +360,7 @@ export const scan = async (directory: string, inputOptions: ScanOptions = {}): P
358360
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
359361
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
360362
scoreOnly: inputOptions.scoreOnly ?? false,
363+
offline: inputOptions.offline ?? false,
361364
includePaths: inputOptions.includePaths,
362365
};
363366

@@ -460,13 +463,14 @@ export const scan = async (directory: string, inputOptions: ScanOptions = {}): P
460463

461464
const elapsedMilliseconds = performance.now() - startTime;
462465

463-
const scoreResult = await calculateScore(diagnostics);
466+
const scoreResult = options.offline ? null : await calculateScore(diagnostics);
467+
const noScoreMessage = options.offline ? OFFLINE_FLAG_MESSAGE : OFFLINE_MESSAGE;
464468

465469
if (options.scoreOnly) {
466470
if (scoreResult) {
467471
logger.log(`${scoreResult.score}`);
468472
} else {
469-
logger.dim(OFFLINE_MESSAGE);
473+
logger.dim(noScoreMessage);
470474
}
471475
return;
472476
}
@@ -478,7 +482,7 @@ export const scan = async (directory: string, inputOptions: ScanOptions = {}): P
478482
printBranding(scoreResult.score);
479483
printScoreGauge(scoreResult.score, scoreResult.label);
480484
} else {
481-
logger.dim(` ${OFFLINE_MESSAGE}`);
485+
logger.dim(` ${noScoreMessage}`);
482486
}
483487
return;
484488
}
@@ -493,5 +497,6 @@ export const scan = async (directory: string, inputOptions: ScanOptions = {}): P
493497
scoreResult,
494498
projectInfo.projectName,
495499
displayedSourceFileCount,
500+
noScoreMessage,
496501
);
497502
};

packages/react-doctor/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface ScanOptions {
8888
deadCode?: boolean;
8989
verbose?: boolean;
9090
scoreOnly?: boolean;
91+
offline?: boolean;
9192
includePaths?: string[];
9293
}
9394

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"use client";
2+
3+
import { useSearchParams } from "next/navigation";
4+
import { Suspense, useEffect, useState } from "react";
5+
6+
const AMI_DEEPLINK_PREFIX = "ami://open-project";
7+
const AMI_RELEASES_URL = "https://github.com/millionco/ami-releases/releases";
8+
const REDIRECT_DELAY_MS = 500;
9+
10+
const OpenPageContent = () => {
11+
const searchParams = useSearchParams();
12+
const deeplink = `${AMI_DEEPLINK_PREFIX}?${searchParams.toString()}`;
13+
const [didAttemptOpen, setDidAttemptOpen] = useState(false);
14+
15+
useEffect(() => {
16+
const timeout = setTimeout(() => {
17+
window.location.href = deeplink;
18+
setDidAttemptOpen(true);
19+
}, REDIRECT_DELAY_MS);
20+
21+
return () => clearTimeout(timeout);
22+
}, [deeplink]);
23+
24+
return (
25+
<div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col items-center justify-center bg-[#0a0a0a] p-6 font-mono text-base leading-relaxed text-neutral-300 sm:p-8 sm:text-lg">
26+
<pre className="mb-6 text-green-400 leading-tight">
27+
{` ┌─────┐\n │ ◠ ◠ │\n │ ▽ │\n └─────┘`}
28+
</pre>
29+
30+
<div className="mb-8 text-center">
31+
<h1 className="mb-2 text-xl text-white">Opening Ami...</h1>
32+
<p className="text-neutral-500">
33+
{didAttemptOpen
34+
? "If Ami didn\u2019t open, it may not be installed."
35+
: "Redirecting to Ami to fix react-doctor issues."}
36+
</p>
37+
</div>
38+
39+
{didAttemptOpen && (
40+
<div className="flex flex-col items-center gap-4">
41+
<a
42+
href={AMI_RELEASES_URL}
43+
target="_blank"
44+
rel="noreferrer"
45+
className="inline-flex items-center gap-2 border border-white/20 bg-white px-4 py-2 text-black transition-all hover:bg-white/90 active:scale-[0.98]"
46+
>
47+
Download Ami
48+
</a>
49+
50+
<div className="mt-4 text-center">
51+
<p className="mb-2 text-sm text-neutral-500">Or open manually:</p>
52+
<a
53+
href={deeplink}
54+
className="break-all text-sm text-blue-400 underline underline-offset-2 hover:text-blue-300"
55+
>
56+
Open deeplink
57+
</a>
58+
</div>
59+
</div>
60+
)}
61+
</div>
62+
);
63+
};
64+
65+
const OpenPage = () => (
66+
<Suspense
67+
fallback={
68+
<div className="flex min-h-screen items-center justify-center bg-[#0a0a0a] font-mono text-neutral-500">
69+
Loading...
70+
</div>
71+
}
72+
>
73+
<OpenPageContent />
74+
</Suspense>
75+
);
76+
77+
export default OpenPage;

0 commit comments

Comments
 (0)