@remix-run/cli 0.0.0 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +80 -3
- package/bootstrap/AGENTS.md +35 -0
- package/bootstrap/README.md +27 -0
- package/bootstrap/app/controllers/auth.tsx +21 -0
- package/bootstrap/app/controllers/home.tsx +26 -0
- package/bootstrap/app/router.ts +10 -0
- package/bootstrap/app/routes.ts +6 -0
- package/bootstrap/app/ui/document.tsx +21 -0
- package/bootstrap/app/ui/layout.tsx +22 -0
- package/bootstrap/app/utils/render.tsx +7 -0
- package/bootstrap/package.json +22 -0
- package/bootstrap/server.ts +37 -0
- package/bootstrap/tsconfig.json +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/bootstrap-project.d.ts +17 -0
- package/dist/lib/bootstrap-project.d.ts.map +1 -0
- package/dist/lib/bootstrap-project.js +137 -0
- package/dist/lib/cli-context.d.ts +11 -0
- package/dist/lib/cli-context.d.ts.map +1 -0
- package/dist/lib/cli-context.js +19 -0
- package/dist/lib/cli.d.ts +6 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +87 -0
- package/dist/lib/commands/completion.d.ts +3 -0
- package/dist/lib/commands/completion.d.ts.map +1 -0
- package/dist/lib/commands/completion.js +51 -0
- package/dist/lib/commands/doctor.d.ts +4 -0
- package/dist/lib/commands/doctor.d.ts.map +1 -0
- package/dist/lib/commands/doctor.js +293 -0
- package/dist/lib/commands/help.d.ts +4 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/help.js +124 -0
- package/dist/lib/commands/new.d.ts +4 -0
- package/dist/lib/commands/new.d.ts.map +1 -0
- package/dist/lib/commands/new.js +70 -0
- package/dist/lib/commands/routes.d.ts +4 -0
- package/dist/lib/commands/routes.d.ts.map +1 -0
- package/dist/lib/commands/routes.js +161 -0
- package/dist/lib/commands/skills.d.ts +6 -0
- package/dist/lib/commands/skills.d.ts.map +1 -0
- package/dist/lib/commands/skills.js +222 -0
- package/dist/lib/commands/test.d.ts +4 -0
- package/dist/lib/commands/test.d.ts.map +1 -0
- package/dist/lib/commands/test.js +19 -0
- package/dist/lib/commands/version.d.ts +4 -0
- package/dist/lib/commands/version.d.ts.map +1 -0
- package/dist/lib/commands/version.js +29 -0
- package/dist/lib/completion.d.ts +12 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +408 -0
- package/dist/lib/contained-path.d.ts +2 -0
- package/dist/lib/contained-path.d.ts.map +1 -0
- package/dist/lib/contained-path.js +10 -0
- package/dist/lib/controller-files.d.ts +14 -0
- package/dist/lib/controller-files.d.ts.map +1 -0
- package/dist/lib/controller-files.js +53 -0
- package/dist/lib/controller-ownership.d.ts +41 -0
- package/dist/lib/controller-ownership.d.ts.map +1 -0
- package/dist/lib/controller-ownership.js +224 -0
- package/dist/lib/display-path.d.ts +2 -0
- package/dist/lib/display-path.d.ts.map +1 -0
- package/dist/lib/display-path.js +12 -0
- package/dist/lib/doctor/controller-findings.d.ts +4 -0
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
- package/dist/lib/doctor/controller-findings.js +132 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
- package/dist/lib/doctor/controller-fix-plans.js +130 -0
- package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
- package/dist/lib/doctor/controller-placeholders.js +183 -0
- package/dist/lib/doctor/controllers.d.ts +8 -0
- package/dist/lib/doctor/controllers.d.ts.map +1 -0
- package/dist/lib/doctor/controllers.js +13 -0
- package/dist/lib/doctor/environment.d.ts +17 -0
- package/dist/lib/doctor/environment.d.ts.map +1 -0
- package/dist/lib/doctor/environment.js +226 -0
- package/dist/lib/doctor/fixes.d.ts +3 -0
- package/dist/lib/doctor/fixes.d.ts.map +1 -0
- package/dist/lib/doctor/fixes.js +65 -0
- package/dist/lib/doctor/project.d.ts +10 -0
- package/dist/lib/doctor/project.d.ts.map +1 -0
- package/dist/lib/doctor/project.js +371 -0
- package/dist/lib/doctor/types.d.ts +48 -0
- package/dist/lib/doctor/types.d.ts.map +1 -0
- package/dist/lib/doctor/types.js +15 -0
- package/dist/lib/errors.d.ts +173 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +318 -0
- package/dist/lib/help-text.d.ts +13 -0
- package/dist/lib/help-text.d.ts.map +1 -0
- package/dist/lib/help-text.js +43 -0
- package/dist/lib/load-route-map-worker.d.ts +2 -0
- package/dist/lib/load-route-map-worker.d.ts.map +1 -0
- package/dist/lib/load-route-map-worker.js +92 -0
- package/dist/lib/parse-args.d.ts +22 -0
- package/dist/lib/parse-args.d.ts.map +1 -0
- package/dist/lib/parse-args.js +35 -0
- package/dist/lib/remix-version.d.ts +2 -0
- package/dist/lib/remix-version.d.ts.map +1 -0
- package/dist/lib/remix-version.js +58 -0
- package/dist/lib/reporter.d.ts +56 -0
- package/dist/lib/reporter.d.ts.map +1 -0
- package/dist/lib/reporter.js +357 -0
- package/dist/lib/route-map.d.ts +38 -0
- package/dist/lib/route-map.d.ts.map +1 -0
- package/dist/lib/route-map.js +195 -0
- package/dist/lib/skills-cache.d.ts +19 -0
- package/dist/lib/skills-cache.d.ts.map +1 -0
- package/dist/lib/skills-cache.js +89 -0
- package/dist/lib/skills.d.ts +30 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +441 -0
- package/dist/lib/terminal.d.ts +17 -0
- package/dist/lib/terminal.d.ts.map +1 -0
- package/dist/lib/terminal.js +66 -0
- package/package.json +48 -5
- package/src/index.ts +1 -0
- package/src/lib/bootstrap-project.ts +222 -0
- package/src/lib/cli-context.ts +38 -0
- package/src/lib/cli.ts +120 -0
- package/src/lib/commands/completion.ts +74 -0
- package/src/lib/commands/doctor.ts +393 -0
- package/src/lib/commands/help.ts +157 -0
- package/src/lib/commands/new.ts +104 -0
- package/src/lib/commands/routes.ts +251 -0
- package/src/lib/commands/skills.ts +306 -0
- package/src/lib/commands/test.ts +25 -0
- package/src/lib/commands/version.ts +39 -0
- package/src/lib/completion.ts +561 -0
- package/src/lib/contained-path.ts +13 -0
- package/src/lib/controller-files.ts +79 -0
- package/src/lib/controller-ownership.ts +397 -0
- package/src/lib/display-path.ts +16 -0
- package/src/lib/doctor/controller-findings.ts +157 -0
- package/src/lib/doctor/controller-fix-plans.ts +184 -0
- package/src/lib/doctor/controller-placeholders.ts +242 -0
- package/src/lib/doctor/controllers.ts +23 -0
- package/src/lib/doctor/environment.ts +289 -0
- package/src/lib/doctor/fixes.ts +84 -0
- package/src/lib/doctor/project.ts +435 -0
- package/src/lib/doctor/types.ts +95 -0
- package/src/lib/errors.ts +379 -0
- package/src/lib/help-text.ts +88 -0
- package/src/lib/load-route-map-worker.ts +110 -0
- package/src/lib/parse-args.ts +82 -0
- package/src/lib/remix-version.ts +80 -0
- package/src/lib/reporter.ts +535 -0
- package/src/lib/route-map.ts +303 -0
- package/src/lib/skills-cache.ts +140 -0
- package/src/lib/skills.ts +706 -0
- package/src/lib/terminal.ts +86 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as process from 'node:process';
|
|
6
|
+
const REMIX_SKILLS_CACHE_VERSION = 1;
|
|
7
|
+
const REMIX_SKILLS_CACHE_REPO = 'remix-run/remix';
|
|
8
|
+
const REMIX_SKILLS_CACHE_REF = 'main';
|
|
9
|
+
export function createSkillsCacheManifest(skillsDir, skills) {
|
|
10
|
+
return {
|
|
11
|
+
ref: REMIX_SKILLS_CACHE_REF,
|
|
12
|
+
repo: REMIX_SKILLS_CACHE_REPO,
|
|
13
|
+
skills,
|
|
14
|
+
skillsDir: path.resolve(skillsDir),
|
|
15
|
+
version: REMIX_SKILLS_CACHE_VERSION,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function getSkillsCacheFilePath(skillsDir) {
|
|
19
|
+
let cacheKey = crypto.createHash('sha256').update(path.resolve(skillsDir)).digest('hex');
|
|
20
|
+
return path.join(getSkillsCacheDir(), `${cacheKey}.json`);
|
|
21
|
+
}
|
|
22
|
+
export async function readSkillsCache(skillsDir) {
|
|
23
|
+
try {
|
|
24
|
+
let filePath = getSkillsCacheFilePath(skillsDir);
|
|
25
|
+
let raw = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
26
|
+
return isSkillsCacheManifest(raw, skillsDir) ? raw : null;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof SyntaxError) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
let nodeError = error;
|
|
33
|
+
if (nodeError.code === 'ENOENT') {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function writeSkillsCache(skillsDir, manifest) {
|
|
40
|
+
let filePath = getSkillsCacheFilePath(skillsDir);
|
|
41
|
+
let tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
42
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
43
|
+
await fs.writeFile(tempPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
44
|
+
await fs.rename(tempPath, filePath);
|
|
45
|
+
}
|
|
46
|
+
function getSkillsCacheDir() {
|
|
47
|
+
if (process.platform === 'win32') {
|
|
48
|
+
let localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
|
|
49
|
+
return path.join(localAppData, 'remix', 'Cache', 'skills');
|
|
50
|
+
}
|
|
51
|
+
if (process.platform === 'darwin') {
|
|
52
|
+
return path.join(process.env.HOME ?? os.homedir(), 'Library', 'Caches', 'remix', 'cli', 'skills');
|
|
53
|
+
}
|
|
54
|
+
let xdgCacheHome = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
|
|
55
|
+
return path.join(xdgCacheHome, 'remix', 'cli', 'skills');
|
|
56
|
+
}
|
|
57
|
+
function isSkillsCacheManifest(value, skillsDir) {
|
|
58
|
+
if (typeof value !== 'object' || value == null) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (Reflect.get(value, 'version') !== REMIX_SKILLS_CACHE_VERSION ||
|
|
62
|
+
Reflect.get(value, 'repo') !== REMIX_SKILLS_CACHE_REPO ||
|
|
63
|
+
Reflect.get(value, 'ref') !== REMIX_SKILLS_CACHE_REF ||
|
|
64
|
+
Reflect.get(value, 'skillsDir') !== path.resolve(skillsDir)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
let skills = Reflect.get(value, 'skills');
|
|
68
|
+
if (typeof skills !== 'object' || skills == null) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
for (let skillEntry of Object.values(skills)) {
|
|
72
|
+
if (typeof skillEntry !== 'object' || skillEntry == null) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
let files = Reflect.get(skillEntry, 'files');
|
|
76
|
+
if (typeof files !== 'object' || files == null) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
for (let fileEntry of Object.values(files)) {
|
|
80
|
+
if (typeof fileEntry !== 'object' ||
|
|
81
|
+
fileEntry == null ||
|
|
82
|
+
typeof Reflect.get(fileEntry, 'localHash') !== 'string' ||
|
|
83
|
+
typeof Reflect.get(fileEntry, 'remoteSha') !== 'string') {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type StepProgressReporter } from './reporter.ts';
|
|
2
|
+
export interface SkillChange {
|
|
3
|
+
action: 'add' | 'replace';
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SkillStatusEntry {
|
|
7
|
+
name: string;
|
|
8
|
+
state: 'installed' | 'missing' | 'outdated';
|
|
9
|
+
}
|
|
10
|
+
interface SkillsResult {
|
|
11
|
+
entries: SkillStatusEntry[];
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
skillsDir: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SkillsOverview extends SkillsResult {
|
|
16
|
+
}
|
|
17
|
+
export interface SkillsInstallResult extends SkillsResult {
|
|
18
|
+
appliedChanges: SkillChange[];
|
|
19
|
+
}
|
|
20
|
+
export interface SkillsOptions {
|
|
21
|
+
progress?: SkillsProgressReporter;
|
|
22
|
+
skillsDir?: string;
|
|
23
|
+
}
|
|
24
|
+
export type SkillsInstallPhase = 'resolve-project-root' | 'fetch-remix-skills-metadata' | 'read-local-skills-cache' | 'compare-local-skills' | 'download-remix-skills-archive' | 'write-updated-skills';
|
|
25
|
+
export type SkillsProgressReporter = StepProgressReporter<SkillsInstallPhase>;
|
|
26
|
+
type FetchImpl = typeof fetch;
|
|
27
|
+
export declare function getSkillsOverview(cwd?: string, fetchImpl?: FetchImpl, options?: SkillsOptions): Promise<SkillsOverview>;
|
|
28
|
+
export declare function installRemixSkills(cwd?: string, fetchImpl?: FetchImpl, options?: SkillsOptions): Promise<SkillsInstallResult>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/lib/skills.ts"],"names":[],"mappings":"AASA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAe1E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,SAAS,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,CAAA;CAC5C;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY;CAAG;AAEvD,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,cAAc,EAAE,WAAW,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,sBAAsB,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,kBAAkB,GAC1B,sBAAsB,GACtB,6BAA6B,GAC7B,yBAAyB,GACzB,sBAAsB,GACtB,+BAA+B,GAC/B,sBAAsB,CAAA;AAE1B,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,kBAAkB,CAAC,CAAA;AA4C7E,KAAK,SAAS,GAAG,OAAO,KAAK,CAAA;AAE7B,wBAAsB,iBAAiB,CACrC,GAAG,GAAE,MAAsB,EAC3B,SAAS,GAAE,SAA4B,EACvC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,cAAc,CAAC,CAOzB;AAED,wBAAsB,kBAAkB,CACtC,GAAG,GAAE,MAAsB,EAC3B,SAAS,GAAE,SAA4B,EACvC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CA4C9B"}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { parseTar } from '@remix-run/tar-parser';
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as process from 'node:process';
|
|
6
|
+
import { gunzipSync } from 'node:zlib';
|
|
7
|
+
import { resolveContainedPath } from "./contained-path.js";
|
|
8
|
+
import { fetchUnavailable, projectRootNotFound, remoteSkillDataMissing } from "./errors.js";
|
|
9
|
+
import { runProgressStep } from "./reporter.js";
|
|
10
|
+
import { createSkillsCacheManifest, readSkillsCache, writeSkillsCache, } from "./skills-cache.js";
|
|
11
|
+
const REMIX_GITHUB_TREE_URL = 'https://api.github.com/repos/remix-run/remix/git/trees/main?recursive=1';
|
|
12
|
+
const REMIX_GITHUB_ARCHIVE_URL = 'https://codeload.github.com/remix-run/remix/tar.gz/refs/heads/main';
|
|
13
|
+
const REMIX_SKILLS_PATH = 'skills/';
|
|
14
|
+
export async function getSkillsOverview(cwd = process.cwd(), fetchImpl = globalThis.fetch, options = {}) {
|
|
15
|
+
let plan = await loadSkillsPlan(cwd, fetchImpl, options);
|
|
16
|
+
return {
|
|
17
|
+
entries: plan.entries,
|
|
18
|
+
projectRoot: plan.projectRoot,
|
|
19
|
+
skillsDir: plan.skillsDir,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export async function installRemixSkills(cwd = process.cwd(), fetchImpl = globalThis.fetch, options = {}) {
|
|
23
|
+
let plan = await loadSkillsPlan(cwd, fetchImpl, options);
|
|
24
|
+
if (plan.pendingChanges.length === 0) {
|
|
25
|
+
options.progress?.skip('write-updated-skills', 'No changes.');
|
|
26
|
+
return {
|
|
27
|
+
appliedChanges: [],
|
|
28
|
+
entries: plan.entries,
|
|
29
|
+
projectRoot: plan.projectRoot,
|
|
30
|
+
skillsDir: plan.skillsDir,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
let targetSkillNames = new Set(plan.pendingChanges.map((change) => change.name));
|
|
34
|
+
let downloadedSkills = await runProgressStep(options.progress, 'download-remix-skills-archive', () => downloadRemoteSkillsArchive(fetchImpl, plan.remoteSkills, targetSkillNames));
|
|
35
|
+
await runProgressStep(options.progress, 'write-updated-skills', async () => {
|
|
36
|
+
await fs.mkdir(plan.skillsDir, { recursive: true });
|
|
37
|
+
for (let change of plan.pendingChanges) {
|
|
38
|
+
let remoteSkill = downloadedSkills.get(change.name);
|
|
39
|
+
if (remoteSkill == null) {
|
|
40
|
+
throw remoteSkillDataMissing(change.name);
|
|
41
|
+
}
|
|
42
|
+
let skillDir = resolveRemoteSkillDir(plan.skillsDir, remoteSkill.name);
|
|
43
|
+
await fs.rm(skillDir, { recursive: true, force: true });
|
|
44
|
+
await writeRemoteSkill(skillDir, remoteSkill);
|
|
45
|
+
}
|
|
46
|
+
let manifest = await buildSkillsCacheManifest(plan.skillsDir, plan.remoteSkills);
|
|
47
|
+
await writeSkillsCache(plan.skillsDir, manifest);
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
appliedChanges: plan.pendingChanges,
|
|
51
|
+
entries: plan.entries,
|
|
52
|
+
projectRoot: plan.projectRoot,
|
|
53
|
+
skillsDir: plan.skillsDir,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function loadSkillsPlan(cwd, fetchImpl, options = {}) {
|
|
57
|
+
if (typeof fetchImpl !== 'function') {
|
|
58
|
+
throw fetchUnavailable();
|
|
59
|
+
}
|
|
60
|
+
let projectRoot = await runProgressStep(options.progress, 'resolve-project-root', () => findProjectRoot(cwd));
|
|
61
|
+
let skillsDir = resolveSkillsDir(projectRoot, options.skillsDir);
|
|
62
|
+
let remoteSkills = await runProgressStep(options.progress, 'fetch-remix-skills-metadata', () => fetchRemoteSkills(fetchImpl));
|
|
63
|
+
let cacheManifest = await runProgressStep(options.progress, 'read-local-skills-cache', () => readSkillsCache(skillsDir));
|
|
64
|
+
let entries = await runProgressStep(options.progress, 'compare-local-skills', async () => Promise.all(remoteSkills.map(async (remoteSkill) => {
|
|
65
|
+
let localSkill = await readLocalSkill(resolveRemoteSkillDir(skillsDir, remoteSkill.name));
|
|
66
|
+
return {
|
|
67
|
+
name: remoteSkill.name,
|
|
68
|
+
state: getSkillState(remoteSkill, localSkill, cacheManifest),
|
|
69
|
+
};
|
|
70
|
+
})));
|
|
71
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
72
|
+
return {
|
|
73
|
+
entries,
|
|
74
|
+
pendingChanges: entries
|
|
75
|
+
.filter((entry) => entry.state !== 'installed')
|
|
76
|
+
.map((entry) => ({
|
|
77
|
+
action: entry.state === 'missing' ? 'add' : 'replace',
|
|
78
|
+
name: entry.name,
|
|
79
|
+
})),
|
|
80
|
+
projectRoot,
|
|
81
|
+
remoteSkills,
|
|
82
|
+
skillsDir,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function fetchRemoteSkills(fetchImpl) {
|
|
86
|
+
let treeResponse = await fetchGitHubJson(fetchImpl, REMIX_GITHUB_TREE_URL, 'Could not fetch Remix skills metadata from GitHub.');
|
|
87
|
+
if (treeResponse.truncated === true) {
|
|
88
|
+
throw new Error('GitHub returned a truncated Remix skills listing.');
|
|
89
|
+
}
|
|
90
|
+
if (!Array.isArray(treeResponse.tree)) {
|
|
91
|
+
throw new Error('Received an invalid Remix skills listing from GitHub.');
|
|
92
|
+
}
|
|
93
|
+
let groupedFiles = new Map();
|
|
94
|
+
for (let entry of treeResponse.tree) {
|
|
95
|
+
if (!isGitHubTreeEntry(entry)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (entry.type !== 'blob' || !entry.path.startsWith(REMIX_SKILLS_PATH)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
let pathParts = entry.path.split('/');
|
|
102
|
+
if (pathParts.length < 3) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
let skillName = pathParts[1];
|
|
106
|
+
let filePath = pathParts.slice(2).join('/');
|
|
107
|
+
if (skillName.length === 0) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
validateRemoteSkillPath(skillName, filePath, entry.path);
|
|
111
|
+
let existing = groupedFiles.get(skillName) ?? [];
|
|
112
|
+
existing.push({
|
|
113
|
+
path: filePath,
|
|
114
|
+
sha: entry.sha,
|
|
115
|
+
});
|
|
116
|
+
groupedFiles.set(skillName, existing);
|
|
117
|
+
}
|
|
118
|
+
let remoteSkills = [...groupedFiles.entries()]
|
|
119
|
+
.filter(([, files]) => files.some((file) => file.path === 'SKILL.md'))
|
|
120
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
121
|
+
.map(([skillName, files]) => ({
|
|
122
|
+
files: files.slice().sort((left, right) => left.path.localeCompare(right.path)),
|
|
123
|
+
name: skillName,
|
|
124
|
+
}));
|
|
125
|
+
if (remoteSkills.length === 0) {
|
|
126
|
+
throw new Error('Could not find any Remix skills on GitHub.');
|
|
127
|
+
}
|
|
128
|
+
return remoteSkills;
|
|
129
|
+
}
|
|
130
|
+
async function downloadRemoteSkillsArchive(fetchImpl, remoteSkills, targetSkillNames) {
|
|
131
|
+
let compressedArchive = await fetchGitHubBytes(fetchImpl, REMIX_GITHUB_ARCHIVE_URL, 'Could not download the Remix skills archive from GitHub.');
|
|
132
|
+
let archive = gunzipSync(compressedArchive);
|
|
133
|
+
let downloadedFiles = new Map();
|
|
134
|
+
await parseTar(archive, async (entry) => {
|
|
135
|
+
if (entry.header.type !== 'file') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let archivePath = getArchiveSkillPath(entry.name);
|
|
139
|
+
if (archivePath == null) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let [skillName, ...fileParts] = archivePath.split('/');
|
|
143
|
+
if (skillName == null || skillName.length === 0 || fileParts.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!targetSkillNames.has(skillName)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let filePath = fileParts.join('/');
|
|
150
|
+
validateRemoteSkillPath(skillName, filePath, archivePath);
|
|
151
|
+
let files = downloadedFiles.get(skillName);
|
|
152
|
+
if (files == null) {
|
|
153
|
+
files = new Map();
|
|
154
|
+
downloadedFiles.set(skillName, files);
|
|
155
|
+
}
|
|
156
|
+
files.set(filePath, new TextDecoder().decode(await entry.bytes()));
|
|
157
|
+
});
|
|
158
|
+
let downloadedSkills = new Map();
|
|
159
|
+
for (let remoteSkill of remoteSkills) {
|
|
160
|
+
if (!targetSkillNames.has(remoteSkill.name)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
let files = downloadedFiles.get(remoteSkill.name);
|
|
164
|
+
if (files == null || files.size !== remoteSkill.files.length) {
|
|
165
|
+
throw new Error(`GitHub returned incomplete archive data for Remix skill: ${remoteSkill.name}.`);
|
|
166
|
+
}
|
|
167
|
+
let validatedFiles = remoteSkill.files.map((file) => {
|
|
168
|
+
let content = files.get(file.path);
|
|
169
|
+
if (content == null) {
|
|
170
|
+
throw new Error(`GitHub returned incomplete archive data for Remix skill file: ${remoteSkill.name}/${file.path}.`);
|
|
171
|
+
}
|
|
172
|
+
let bytes = Buffer.from(content, 'utf8');
|
|
173
|
+
if (computeGitBlobSha(bytes) !== file.sha) {
|
|
174
|
+
throw new Error(`GitHub returned Remix skill content that did not match the metadata listing for ${remoteSkill.name}/${file.path}. Please try again.`);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
content,
|
|
178
|
+
path: file.path,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
downloadedSkills.set(remoteSkill.name, {
|
|
182
|
+
files: validatedFiles,
|
|
183
|
+
name: remoteSkill.name,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return downloadedSkills;
|
|
187
|
+
}
|
|
188
|
+
async function fetchGitHubJson(fetchImpl, url, failureMessage) {
|
|
189
|
+
let response = await fetchImpl(url, { headers: createGitHubHeaders() });
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
throw createGitHubRequestError(response, failureMessage);
|
|
192
|
+
}
|
|
193
|
+
return (await response.json());
|
|
194
|
+
}
|
|
195
|
+
async function fetchGitHubBytes(fetchImpl, url, failureMessage) {
|
|
196
|
+
let response = await fetchImpl(url, { headers: createGitHubHeaders() });
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw createGitHubRequestError(response, failureMessage);
|
|
199
|
+
}
|
|
200
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
201
|
+
}
|
|
202
|
+
function createGitHubHeaders() {
|
|
203
|
+
let token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
204
|
+
return {
|
|
205
|
+
Accept: 'application/vnd.github+json',
|
|
206
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
207
|
+
'User-Agent': '@remix-run/cli',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createGitHubRequestError(response, failureMessage) {
|
|
211
|
+
if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
|
|
212
|
+
let resetText = getGitHubRateLimitResetText(response);
|
|
213
|
+
return new Error(`${failureMessage} GitHub API rate limit exceeded.${resetText} Set GITHUB_TOKEN or GH_TOKEN to use a higher authenticated GitHub rate limit.`);
|
|
214
|
+
}
|
|
215
|
+
return new Error(`${failureMessage} ${response.status} ${response.statusText}`.trim());
|
|
216
|
+
}
|
|
217
|
+
function getGitHubRateLimitResetText(response) {
|
|
218
|
+
let reset = Number(response.headers.get('x-ratelimit-reset'));
|
|
219
|
+
if (!Number.isFinite(reset)) {
|
|
220
|
+
return '';
|
|
221
|
+
}
|
|
222
|
+
return ` The rate limit resets at ${new Date(reset * 1000).toISOString()}.`;
|
|
223
|
+
}
|
|
224
|
+
function isGitHubTreeEntry(value) {
|
|
225
|
+
return (typeof value === 'object' &&
|
|
226
|
+
value != null &&
|
|
227
|
+
typeof Reflect.get(value, 'path') === 'string' &&
|
|
228
|
+
typeof Reflect.get(value, 'sha') === 'string' &&
|
|
229
|
+
(Reflect.get(value, 'type') === 'blob' || Reflect.get(value, 'type') === 'tree'));
|
|
230
|
+
}
|
|
231
|
+
async function findProjectRoot(startDir) {
|
|
232
|
+
let currentDir = path.resolve(startDir);
|
|
233
|
+
while (true) {
|
|
234
|
+
if ((await pathExists(path.join(currentDir, 'package.json'))) ||
|
|
235
|
+
(await pathExists(path.join(currentDir, '.agents'))) ||
|
|
236
|
+
(await pathExists(path.join(currentDir, '.git')))) {
|
|
237
|
+
return currentDir;
|
|
238
|
+
}
|
|
239
|
+
let parentDir = path.dirname(currentDir);
|
|
240
|
+
if (parentDir === currentDir) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
currentDir = parentDir;
|
|
244
|
+
}
|
|
245
|
+
throw projectRootNotFound(startDir);
|
|
246
|
+
}
|
|
247
|
+
function resolveSkillsDir(projectRoot, skillsDir) {
|
|
248
|
+
if (skillsDir == null || skillsDir.length === 0) {
|
|
249
|
+
return path.join(projectRoot, '.agents', 'skills');
|
|
250
|
+
}
|
|
251
|
+
if (path.isAbsolute(skillsDir)) {
|
|
252
|
+
return skillsDir;
|
|
253
|
+
}
|
|
254
|
+
return path.resolve(projectRoot, skillsDir);
|
|
255
|
+
}
|
|
256
|
+
async function pathExists(filePath) {
|
|
257
|
+
try {
|
|
258
|
+
await fs.access(filePath);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
let nodeError = error;
|
|
263
|
+
if (nodeError.code === 'ENOENT') {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function readLocalSkill(skillDir) {
|
|
270
|
+
try {
|
|
271
|
+
let stats = await fs.stat(skillDir);
|
|
272
|
+
if (!stats.isDirectory()) {
|
|
273
|
+
return {
|
|
274
|
+
exists: true,
|
|
275
|
+
fileHashes: new Map(),
|
|
276
|
+
isDirectory: false,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
exists: true,
|
|
281
|
+
fileHashes: await readLocalFiles(skillDir),
|
|
282
|
+
isDirectory: true,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
let nodeError = error;
|
|
287
|
+
if (nodeError.code === 'ENOENT') {
|
|
288
|
+
return {
|
|
289
|
+
exists: false,
|
|
290
|
+
fileHashes: new Map(),
|
|
291
|
+
isDirectory: false,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function readLocalFiles(rootDir, relativeDir = '') {
|
|
298
|
+
let files = new Map();
|
|
299
|
+
let dirPath = relativeDir.length === 0 ? rootDir : path.join(rootDir, relativeDir);
|
|
300
|
+
let entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
301
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
302
|
+
for (let entry of entries) {
|
|
303
|
+
let relativePath = relativeDir.length === 0 ? entry.name : `${relativeDir}/${entry.name}`;
|
|
304
|
+
let entryPath = path.join(rootDir, relativePath);
|
|
305
|
+
if (entry.isDirectory()) {
|
|
306
|
+
let nestedFiles = await readLocalFiles(rootDir, relativePath);
|
|
307
|
+
for (let [nestedPath, hash] of nestedFiles) {
|
|
308
|
+
files.set(nestedPath, hash);
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (entry.isFile()) {
|
|
313
|
+
files.set(relativePath, computeLocalContentHash(await fs.readFile(entryPath)));
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
files.set(relativePath, '');
|
|
317
|
+
}
|
|
318
|
+
return files;
|
|
319
|
+
}
|
|
320
|
+
function getSkillState(remoteSkill, localSkill, cacheManifest) {
|
|
321
|
+
if (!localSkill.exists) {
|
|
322
|
+
return 'missing';
|
|
323
|
+
}
|
|
324
|
+
if (!localSkill.isDirectory) {
|
|
325
|
+
return 'outdated';
|
|
326
|
+
}
|
|
327
|
+
let cachedSkill = cacheManifest?.skills[remoteSkill.name];
|
|
328
|
+
if (cachedSkill == null) {
|
|
329
|
+
return 'outdated';
|
|
330
|
+
}
|
|
331
|
+
let cachedFiles = new Map(Object.entries(cachedSkill.files));
|
|
332
|
+
if (localSkill.fileHashes.size !== remoteSkill.files.length ||
|
|
333
|
+
cachedFiles.size !== remoteSkill.files.length) {
|
|
334
|
+
return 'outdated';
|
|
335
|
+
}
|
|
336
|
+
for (let file of remoteSkill.files) {
|
|
337
|
+
let localHash = localSkill.fileHashes.get(file.path);
|
|
338
|
+
let cachedFile = cachedFiles.get(file.path);
|
|
339
|
+
if (localHash == null ||
|
|
340
|
+
cachedFile == null ||
|
|
341
|
+
cachedFile.localHash !== localHash ||
|
|
342
|
+
cachedFile.remoteSha !== file.sha) {
|
|
343
|
+
return 'outdated';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return 'installed';
|
|
347
|
+
}
|
|
348
|
+
async function buildSkillsCacheManifest(skillsDir, remoteSkills) {
|
|
349
|
+
let cachedSkills = {};
|
|
350
|
+
for (let remoteSkill of remoteSkills) {
|
|
351
|
+
let localSkill = await readLocalSkill(resolveRemoteSkillDir(skillsDir, remoteSkill.name));
|
|
352
|
+
if (!localSkill.exists || !localSkill.isDirectory) {
|
|
353
|
+
throw remoteSkillDataMissing(remoteSkill.name);
|
|
354
|
+
}
|
|
355
|
+
let cachedFiles = {};
|
|
356
|
+
for (let file of remoteSkill.files) {
|
|
357
|
+
let localHash = localSkill.fileHashes.get(file.path);
|
|
358
|
+
if (localHash == null) {
|
|
359
|
+
throw new Error(`Installed Remix skill is missing file data for ${remoteSkill.name}/${file.path}.`);
|
|
360
|
+
}
|
|
361
|
+
cachedFiles[file.path] = {
|
|
362
|
+
localHash,
|
|
363
|
+
remoteSha: file.sha,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
cachedSkills[remoteSkill.name] = {
|
|
367
|
+
files: cachedFiles,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return createSkillsCacheManifest(skillsDir, cachedSkills);
|
|
371
|
+
}
|
|
372
|
+
async function writeRemoteSkill(skillDir, remoteSkill) {
|
|
373
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
374
|
+
for (let file of remoteSkill.files) {
|
|
375
|
+
let filePath = resolveRemoteSkillFilePath(skillDir, file.path);
|
|
376
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
377
|
+
await fs.writeFile(filePath, file.content, 'utf8');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function resolveRemoteSkillDir(skillsDir, skillName) {
|
|
381
|
+
validateRemoteSkillName(skillName, skillName);
|
|
382
|
+
return resolveRemoteSkillPath(skillsDir, skillName);
|
|
383
|
+
}
|
|
384
|
+
function resolveRemoteSkillFilePath(skillDir, filePath) {
|
|
385
|
+
validateRemoteSkillFilePath(filePath, filePath);
|
|
386
|
+
return resolveRemoteSkillPath(skillDir, filePath);
|
|
387
|
+
}
|
|
388
|
+
function resolveRemoteSkillPath(rootDir, relativePath) {
|
|
389
|
+
try {
|
|
390
|
+
return resolveContainedPath(rootDir, relativePath);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
if (error instanceof Error && error.message.includes('escapes the allowed root')) {
|
|
394
|
+
throw new Error(`GitHub returned an invalid Remix skill path: ${relativePath}`);
|
|
395
|
+
}
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function validateRemoteSkillPath(skillName, filePath, remotePath) {
|
|
400
|
+
validateRemoteSkillName(skillName, remotePath);
|
|
401
|
+
validateRemoteSkillFilePath(filePath, remotePath);
|
|
402
|
+
}
|
|
403
|
+
function validateRemoteSkillName(skillName, remotePath) {
|
|
404
|
+
if (isSafeRemoteSkillPath(skillName, { allowNested: false })) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
throw new Error(`GitHub returned an invalid Remix skill path: ${remotePath}`);
|
|
408
|
+
}
|
|
409
|
+
function validateRemoteSkillFilePath(filePath, remotePath) {
|
|
410
|
+
if (isSafeRemoteSkillPath(filePath, { allowNested: true })) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
throw new Error(`GitHub returned an invalid Remix skill path: ${remotePath}`);
|
|
414
|
+
}
|
|
415
|
+
function isSafeRemoteSkillPath(remotePath, options) {
|
|
416
|
+
if (remotePath.length === 0 ||
|
|
417
|
+
remotePath.includes('\\') ||
|
|
418
|
+
path.posix.isAbsolute(remotePath) ||
|
|
419
|
+
path.win32.isAbsolute(remotePath)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
let pathParts = remotePath.split('/');
|
|
423
|
+
if (!options.allowNested && pathParts.length !== 1) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
return pathParts.every((pathPart) => pathPart.length > 0 && pathPart !== '.' && pathPart !== '..');
|
|
427
|
+
}
|
|
428
|
+
function getArchiveSkillPath(entryName) {
|
|
429
|
+
let marker = `/${REMIX_SKILLS_PATH}`;
|
|
430
|
+
let markerIndex = entryName.indexOf(marker);
|
|
431
|
+
if (markerIndex === -1) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
return entryName.slice(markerIndex + marker.length);
|
|
435
|
+
}
|
|
436
|
+
function computeLocalContentHash(bytes) {
|
|
437
|
+
return crypto.createHash('sha256').update(bytes).digest('hex');
|
|
438
|
+
}
|
|
439
|
+
function computeGitBlobSha(bytes) {
|
|
440
|
+
return crypto.createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex');
|
|
441
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function configureColors(options: {
|
|
2
|
+
disabled: boolean;
|
|
3
|
+
}): void;
|
|
4
|
+
export declare function bold(text: string, target?: NodeJS.WriteStream): string;
|
|
5
|
+
export declare function lightGreen(text: string, target?: NodeJS.WriteStream): string;
|
|
6
|
+
export declare function lightBlue(text: string, target?: NodeJS.WriteStream): string;
|
|
7
|
+
export declare function boldLightBlue(text: string, target?: NodeJS.WriteStream): string;
|
|
8
|
+
export declare function lightGray(text: string, target?: NodeJS.WriteStream): string;
|
|
9
|
+
export declare function lightMagenta(text: string, target?: NodeJS.WriteStream): string;
|
|
10
|
+
export declare function lightRed(text: string, target?: NodeJS.WriteStream): string;
|
|
11
|
+
export declare function lightYellow(text: string, target?: NodeJS.WriteStream): string;
|
|
12
|
+
export declare function reset(target: NodeJS.WriteStream): string;
|
|
13
|
+
export declare function remixWordmark(target?: NodeJS.WriteStream): string;
|
|
14
|
+
export declare function clearCurrentLine(): string;
|
|
15
|
+
export declare function restoreTerminalFormatting(): void;
|
|
16
|
+
export declare function canUseAnsi(target: NodeJS.WriteStream): boolean;
|
|
17
|
+
//# sourceMappingURL=terminal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/lib/terminal.ts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEpE;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAEtF;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE5F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE3F;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE/F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE3F;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE9F;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE1F;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAE7F;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,MAAM,CAExD;AAED,wBAAgB,aAAa,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAYjF;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAShD;AAUD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,OAAO,CAE9D"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as process from 'node:process';
|
|
2
|
+
import { ansi, createTerminal, shouldUseColors } from '@remix-run/terminal';
|
|
3
|
+
let colorDisabledByFlag = false;
|
|
4
|
+
export function configureColors(options) {
|
|
5
|
+
colorDisabledByFlag = options.disabled;
|
|
6
|
+
}
|
|
7
|
+
export function bold(text, target = process.stdout) {
|
|
8
|
+
return paint(text, ansi.bold, target);
|
|
9
|
+
}
|
|
10
|
+
export function lightGreen(text, target = process.stdout) {
|
|
11
|
+
return paint(text, ansi.greenBright, target);
|
|
12
|
+
}
|
|
13
|
+
export function lightBlue(text, target = process.stdout) {
|
|
14
|
+
return paint(text, ansi.blueBright, target);
|
|
15
|
+
}
|
|
16
|
+
export function boldLightBlue(text, target = process.stdout) {
|
|
17
|
+
return isColorDisabled(target) ? text : `${ansi.bold}${ansi.blueBright}${text}${ansi.reset}`;
|
|
18
|
+
}
|
|
19
|
+
export function lightGray(text, target = process.stdout) {
|
|
20
|
+
return paint(text, ansi.gray, target);
|
|
21
|
+
}
|
|
22
|
+
export function lightMagenta(text, target = process.stdout) {
|
|
23
|
+
return paint(text, ansi.magentaBright, target);
|
|
24
|
+
}
|
|
25
|
+
export function lightRed(text, target = process.stdout) {
|
|
26
|
+
return paint(text, ansi.redBright, target);
|
|
27
|
+
}
|
|
28
|
+
export function lightYellow(text, target = process.stdout) {
|
|
29
|
+
return paint(text, ansi.yellowBright, target);
|
|
30
|
+
}
|
|
31
|
+
export function reset(target) {
|
|
32
|
+
return isColorDisabled(target) ? '' : ansi.reset;
|
|
33
|
+
}
|
|
34
|
+
export function remixWordmark(target = process.stdout) {
|
|
35
|
+
if (isColorDisabled(target)) {
|
|
36
|
+
return 'REMIX';
|
|
37
|
+
}
|
|
38
|
+
return [
|
|
39
|
+
paint('R', ansi.blueBright, target),
|
|
40
|
+
paint('E', ansi.greenBright, target),
|
|
41
|
+
paint('M', ansi.yellowBright, target),
|
|
42
|
+
paint('I', ansi.magentaBright, target),
|
|
43
|
+
paint('X', ansi.redBright, target),
|
|
44
|
+
].join('');
|
|
45
|
+
}
|
|
46
|
+
export function clearCurrentLine() {
|
|
47
|
+
return `\r${ansi.clearLine}`;
|
|
48
|
+
}
|
|
49
|
+
export function restoreTerminalFormatting() {
|
|
50
|
+
if (canUseAnsi(process.stdout)) {
|
|
51
|
+
createTerminal({ stdout: process.stdout }).write(ansi.reset);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (canUseAnsi(process.stderr)) {
|
|
55
|
+
createTerminal({ stderr: process.stderr }).error(ansi.reset);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function paint(text, colorCode, target) {
|
|
59
|
+
return isColorDisabled(target) ? text : `${colorCode}${text}${ansi.reset}`;
|
|
60
|
+
}
|
|
61
|
+
function isColorDisabled(target) {
|
|
62
|
+
return colorDisabledByFlag || !shouldUseColors({ stream: target });
|
|
63
|
+
}
|
|
64
|
+
export function canUseAnsi(target) {
|
|
65
|
+
return process.env.TERM !== 'dumb' && target.isTTY === true;
|
|
66
|
+
}
|