+185
-81
@@ -10,3 +10,4 @@ #!/usr/bin/env node | ||
| //#region package.json | ||
| var version = "1.1.0"; | ||
| var name = "greenly"; | ||
| var version = "1.1.1"; | ||
| //#endregion | ||
@@ -97,2 +98,87 @@ //#region src/lib/args.ts | ||
| //#endregion | ||
| //#region src/lib/utils.ts | ||
| function installCommand(pm) { | ||
| switch (pm) { | ||
| case "pnpm": return "pnpm add -D greenly@latest"; | ||
| case "yarn": return "yarn add -D greenly@latest"; | ||
| case "bun": return "bun add -d greenly@latest"; | ||
| default: return "npm install -D greenly@latest"; | ||
| } | ||
| } | ||
| function detectLockfiles(cwd) { | ||
| return [ | ||
| "pnpm-lock.yaml", | ||
| "yarn.lock", | ||
| "package-lock.json", | ||
| "bun.lockb", | ||
| "bun.lock" | ||
| ].filter((f) => existsSync(join(cwd, f))); | ||
| } | ||
| function detectPackageManager(userAgent, lockfiles) { | ||
| const ua = userAgent ?? ""; | ||
| if (ua.startsWith("pnpm")) return "pnpm"; | ||
| if (ua.startsWith("yarn")) return "yarn"; | ||
| if (ua.startsWith("bun")) return "bun"; | ||
| if (ua.startsWith("npm")) return "npm"; | ||
| if (lockfiles.includes("pnpm-lock.yaml")) return "pnpm"; | ||
| if (lockfiles.includes("yarn.lock")) return "yarn"; | ||
| if (lockfiles.includes("bun.lockb") || lockfiles.includes("bun.lock")) return "bun"; | ||
| return "npm"; | ||
| } | ||
| //#endregion | ||
| //#region src/lib/version.ts | ||
| const parse = (v) => { | ||
| const [core = "", pre = ""] = v.trim().replace(/^[v^~>=< ]+/, "").split("-", 2); | ||
| return { | ||
| nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), | ||
| pre | ||
| }; | ||
| }; | ||
| function compareVersions(a, b) { | ||
| const pa = parse(a); | ||
| const pb = parse(b); | ||
| for (let i = 0; i < 3; i++) { | ||
| const diff = (pa.nums[i] ?? 0) - (pb.nums[i] ?? 0); | ||
| if (diff !== 0) return diff > 0 ? 1 : -1; | ||
| } | ||
| if (pa.pre === pb.pre) return 0; | ||
| if (pa.pre === "") return 1; | ||
| if (pb.pre === "") return -1; | ||
| return pa.pre > pb.pre ? 1 : -1; | ||
| } | ||
| function isNewer(latest, current) { | ||
| return compareVersions(latest, current) > 0; | ||
| } | ||
| async function fetchLatestVersion(pkg, timeoutMs = 3e3) { | ||
| try { | ||
| const controller = new AbortController(); | ||
| const timer = setTimeout(() => controller.abort(), timeoutMs); | ||
| try { | ||
| const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`, { | ||
| signal: controller.signal, | ||
| headers: { accept: "application/vnd.npm.install-v1+json" } | ||
| }); | ||
| if (!res.ok) return null; | ||
| const data = await res.json(); | ||
| if (data && typeof data === "object" && "version" in data) { | ||
| const version = data.version; | ||
| return typeof version === "string" ? version : null; | ||
| } | ||
| return null; | ||
| } finally { | ||
| clearTimeout(timer); | ||
| } | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| async function checkForUpdate(pkg, current) { | ||
| const latest = await fetchLatestVersion(pkg); | ||
| if (latest && isNewer(latest, current)) return { | ||
| current, | ||
| latest | ||
| }; | ||
| return null; | ||
| } | ||
| //#endregion | ||
| //#region src/lib/init.ts | ||
@@ -107,2 +193,3 @@ const execAsync = promisify(exec); | ||
| } | ||
| const dep = (name) => (deps) => deps.has(name); | ||
| const CHECK_PRESETS = [ | ||
@@ -112,5 +199,6 @@ { | ||
| label: "TypeScript (tsc)", | ||
| detect: dep("typescript"), | ||
| build: (c) => ({ | ||
| name: "TypeScript", | ||
| command: scriptCommand(c, ["typecheck", "type-check"]) ?? `${c.exec} tsc --noEmit${c.isNext ? " --incremental false" : ""}` | ||
| command: scriptCommand(c, ["typecheck", "type-check"]) ?? `${c.run} tsc --noEmit${dep("next")(c.deps) ? " --incremental false" : ""}` | ||
| }) | ||
@@ -121,6 +209,7 @@ }, | ||
| label: "Oxfmt", | ||
| detect: dep("oxfmt"), | ||
| build: (c) => ({ | ||
| name: "Oxfmt", | ||
| command: scriptCommand(c, ["fmt:check", "format:check"]) ?? `${c.exec} oxfmt --check`, | ||
| onFail: scriptCommand(c, ["fmt", "format"]) ?? `${c.exec} oxfmt` | ||
| command: scriptCommand(c, ["fmt:check", "format:check"]) ?? `${c.run} oxfmt --check`, | ||
| onFail: scriptCommand(c, ["fmt", "format"]) ?? `${c.run} oxfmt` | ||
| }) | ||
@@ -131,6 +220,7 @@ }, | ||
| label: "Prettier", | ||
| detect: dep("prettier"), | ||
| build: (c) => ({ | ||
| name: "Prettier", | ||
| command: scriptCommand(c, ["fmt:check", "format:check"]) ?? `${c.exec} prettier --check .`, | ||
| onFail: scriptCommand(c, ["fmt", "format"]) ?? `${c.exec} prettier --write .` | ||
| command: scriptCommand(c, ["fmt:check", "format:check"]) ?? `${c.run} prettier --check .`, | ||
| onFail: scriptCommand(c, ["fmt", "format"]) ?? `${c.run} prettier --write .` | ||
| }) | ||
@@ -141,5 +231,6 @@ }, | ||
| label: "Oxlint", | ||
| detect: dep("oxlint"), | ||
| build: (c) => ({ | ||
| name: "Oxlint", | ||
| command: scriptCommand(c, ["lint"]) ?? `${c.exec} oxlint` | ||
| command: scriptCommand(c, ["lint"]) ?? `${c.run} oxlint` | ||
| }) | ||
@@ -150,5 +241,6 @@ }, | ||
| label: "ESLint", | ||
| detect: dep("eslint"), | ||
| build: (c) => ({ | ||
| name: "ESLint", | ||
| command: scriptCommand(c, ["lint"]) ?? `${c.exec} eslint .` | ||
| command: scriptCommand(c, ["lint"]) ?? `${c.run} eslint .` | ||
| }) | ||
@@ -159,8 +251,18 @@ }, | ||
| label: "Tests (Vitest)", | ||
| detect: dep("vitest"), | ||
| build: (c) => ({ | ||
| name: "Tests", | ||
| command: scriptCommand(c, ["test"]) ?? `${c.exec} vitest run` | ||
| command: scriptCommand(c, ["test"]) ?? `${c.run} vitest run` | ||
| }) | ||
| }, | ||
| { | ||
| value: "expo-doctor", | ||
| label: "Expo Doctor", | ||
| detect: dep("expo"), | ||
| build: (c) => ({ | ||
| name: "Expo Doctor", | ||
| command: scriptCommand(c, ["doctor"]) ?? `${c.exec} expo-doctor` | ||
| }) | ||
| }, | ||
| { | ||
| value: "build", | ||
@@ -172,2 +274,11 @@ label: "Build", | ||
| }) | ||
| }, | ||
| { | ||
| value: "react-doctor", | ||
| label: "React Doctor", | ||
| detect: dep("react"), | ||
| build: (c) => ({ | ||
| name: "React Doctor", | ||
| command: `${dep("react-doctor")(c.deps) ? c.run : c.exec} react-doctor --verbose` | ||
| }) | ||
| } | ||
@@ -178,7 +289,7 @@ ]; | ||
| case "pnpm": return { | ||
| exec: "pnpm", | ||
| exec: "pnpx", | ||
| run: "pnpm" | ||
| }; | ||
| case "yarn": return { | ||
| exec: "yarn", | ||
| exec: "yarn dlx", | ||
| run: "yarn" | ||
@@ -196,25 +307,6 @@ }; | ||
| } | ||
| function installCommand(pm) { | ||
| switch (pm) { | ||
| case "pnpm": return "pnpm add -D greenly@latest"; | ||
| case "yarn": return "yarn add -D greenly@latest"; | ||
| case "bun": return "bun add -d greenly@latest"; | ||
| default: return "npm install -D greenly@latest"; | ||
| } | ||
| } | ||
| function detectPackageManager(userAgent, lockfiles) { | ||
| const ua = userAgent ?? ""; | ||
| if (ua.startsWith("pnpm")) return "pnpm"; | ||
| if (ua.startsWith("yarn")) return "yarn"; | ||
| if (ua.startsWith("bun")) return "bun"; | ||
| if (ua.startsWith("npm")) return "npm"; | ||
| if (lockfiles.includes("pnpm-lock.yaml")) return "pnpm"; | ||
| if (lockfiles.includes("yarn.lock")) return "yarn"; | ||
| if (lockfiles.includes("bun.lockb") || lockfiles.includes("bun.lock")) return "bun"; | ||
| return "npm"; | ||
| } | ||
| function buildChecks(selected, pm, opts = {}) { | ||
| const ctx = { | ||
| ...pmContext(pm), | ||
| isNext: opts.isNext ?? false, | ||
| deps: opts.deps ?? new Set(), | ||
| scripts: opts.scripts ?? new Set() | ||
@@ -224,10 +316,2 @@ }; | ||
| } | ||
| function isNextProject(pkg, hasNextConfig) { | ||
| if (hasNextConfig) return true; | ||
| const hasNextDep = (field) => { | ||
| const deps = pkg?.[field]; | ||
| return isRecord(deps) && "next" in deps; | ||
| }; | ||
| return hasNextDep("dependencies") || hasNextDep("devDependencies"); | ||
| } | ||
| function packageScripts(pkg) { | ||
@@ -237,20 +321,38 @@ const scripts = pkg?.scripts; | ||
| } | ||
| function depRecord(pkg, field) { | ||
| const deps = pkg?.[field]; | ||
| return isRecord(deps) ? deps : null; | ||
| } | ||
| function installedDependencies(pkg) { | ||
| const names = new Set(); | ||
| for (const field of ["dependencies", "devDependencies"]) { | ||
| const deps = pkg?.[field]; | ||
| if (isRecord(deps)) for (const key of Object.keys(deps)) names.add(key); | ||
| const deps = depRecord(pkg, field); | ||
| if (deps) for (const key of Object.keys(deps)) names.add(key); | ||
| } | ||
| return names; | ||
| } | ||
| function availablePresets(installed) { | ||
| const hidden = new Set(); | ||
| const prefer = (a, b) => { | ||
| if (installed.has(a) && !installed.has(b)) hidden.add(b); | ||
| if (installed.has(b) && !installed.has(a)) hidden.add(a); | ||
| function greenlyLocation(pkg) { | ||
| const inField = (field) => { | ||
| const deps = depRecord(pkg, field); | ||
| return deps !== null && "greenly" in deps; | ||
| }; | ||
| prefer("oxlint", "eslint"); | ||
| prefer("oxfmt", "prettier"); | ||
| return CHECK_PRESETS.filter((p) => !hidden.has(p.value)); | ||
| if (inField("devDependencies")) return "dev"; | ||
| if (inField("dependencies")) return "prod"; | ||
| return "none"; | ||
| } | ||
| function declaredGreenlyVersion(pkg) { | ||
| for (const field of ["devDependencies", "dependencies"]) { | ||
| const version = depRecord(pkg, field)?.greenly; | ||
| if (typeof version === "string") return version; | ||
| } | ||
| return null; | ||
| } | ||
| function shouldOfferInstall(opts) { | ||
| const { location, declaredVersion, latestVersion } = opts; | ||
| if (location === "dev" && declaredVersion && latestVersion && !isNewer(latestVersion, declaredVersion)) return false; | ||
| return true; | ||
| } | ||
| function availablePresets(installed) { | ||
| return CHECK_PRESETS.filter((p) => !p.detect || p.detect(installed)); | ||
| } | ||
| function configFileName(ext) { | ||
@@ -298,21 +400,2 @@ return `greenly.config.${ext}`; | ||
| } | ||
| function detectLockfiles(cwd) { | ||
| return [ | ||
| "pnpm-lock.yaml", | ||
| "yarn.lock", | ||
| "package-lock.json", | ||
| "bun.lockb", | ||
| "bun.lock" | ||
| ].filter((f) => existsSync(join(cwd, f))); | ||
| } | ||
| function hasNextConfigFile(cwd) { | ||
| return [ | ||
| "js", | ||
| "mjs", | ||
| "cjs", | ||
| "ts", | ||
| "mts", | ||
| "cts" | ||
| ].some((e) => existsSync(join(cwd, `next.config.${e}`))); | ||
| } | ||
| function ensure(value) { | ||
@@ -331,2 +414,3 @@ if (isCancel(value)) { | ||
| const pm = detectPackageManager(process.env.npm_config_user_agent, detectLockfiles(cwd)); | ||
| const latestPromise = fetchLatestVersion("greenly"); | ||
| const name = ensure(await text({ | ||
@@ -350,3 +434,4 @@ message: "Project name (shown in the banner)", | ||
| })); | ||
| const presets = availablePresets(installedDependencies(pkg)); | ||
| const installed = installedDependencies(pkg); | ||
| const presets = availablePresets(installed); | ||
| const selected = ensure(await multiselect({ | ||
@@ -360,3 +445,10 @@ message: "Select the checks to include", | ||
| })); | ||
| const doInstall = ensure(await confirm({ | ||
| const declaredVersion = declaredGreenlyVersion(pkg); | ||
| const latestVersion = await latestPromise; | ||
| const alreadyLatest = !shouldOfferInstall({ | ||
| location: greenlyLocation(pkg), | ||
| declaredVersion, | ||
| latestVersion | ||
| }); | ||
| const doInstall = alreadyLatest ? false : ensure(await confirm({ | ||
| message: `Install greenly now with ${pm}?`, | ||
@@ -376,8 +468,6 @@ initialValue: true | ||
| } | ||
| const isNext = isNextProject(pkg, hasNextConfigFile(cwd)); | ||
| if (isNext && selected.includes("typescript")) log.info(colors.dim("Detected Next.js, using tsc --incremental false")); | ||
| const content = renderConfig({ | ||
| name, | ||
| checks: buildChecks(selected, pm, { | ||
| isNext, | ||
| deps: installed, | ||
| scripts: packageScripts(pkg) | ||
@@ -412,3 +502,4 @@ }), | ||
| } | ||
| } else log.info(`Skipped install. Run "${installCommand(pm)}" when ready.`); | ||
| } else if (alreadyLatest) log.info(`greenly ${colors.bold(declaredVersion ?? "")} already a devDependency at latest, skipped install.`); | ||
| else log.info(`Skipped install. Run "${installCommand(pm)}" when ready.`); | ||
| const runCmd = `${pmContext(pm).run} ${scriptName}`; | ||
@@ -439,3 +530,4 @@ outro(`Done. Run ${colors.bold(runCmd)} to run your checks.`); | ||
| ok: false, | ||
| stderr: colors.red(formatThrown(error)) | ||
| stderr: colors.red(formatThrown(error)), | ||
| error | ||
| }; | ||
@@ -465,3 +557,4 @@ } | ||
| ok: false, | ||
| stderr | ||
| stderr, | ||
| error | ||
| }; | ||
@@ -507,2 +600,5 @@ } | ||
| } | ||
| function fixCommand(check) { | ||
| return typeof check.onFail === "string" ? check.onFail : "fix function"; | ||
| } | ||
| async function runChecks(config, options = {}) { | ||
@@ -522,3 +618,3 @@ const { autoFix = false, interactive = true } = options; | ||
| console.log(` ${colors.cyan(commandLine(check.command))}\n`); | ||
| const { ok, stderr } = await runCommand(check.command); | ||
| const { ok, stderr, error } = await runCommand(check.command); | ||
| if (ok) { | ||
@@ -570,5 +666,4 @@ console.log(`\n${colors.green(`✔ PASSED: ${check.name}`)}\n`); | ||
| } | ||
| const fixDisplay = typeof check.onFail === "string" ? check.onFail : "fix function"; | ||
| console.log(`\n ${colors.cyan(`$ ${fixDisplay}`)}\n`); | ||
| if (await runFix(check, new Error(`${check.name} failed`))) { | ||
| console.log(`\n ${colors.cyan(`$ ${fixCommand(check)}`)}\n`); | ||
| if (await runFix(check, error)) { | ||
| console.log(`\n${colors.green(`✔ Auto-fixed: ${check.name}`)}\n`); | ||
@@ -634,2 +729,7 @@ results.push({ | ||
| `; | ||
| function printUpdateNotice(info) { | ||
| const pm = detectPackageManager(process.env.npm_config_user_agent, detectLockfiles(process.cwd())); | ||
| console.log(colors.yellow(`Update available: greenly ${colors.dim(info.current)} -> ${colors.bold(info.latest)}`)); | ||
| console.log(colors.dim(`Run ${colors.bold(installCommand(pm))} to update.`) + "\n"); | ||
| } | ||
| async function main() { | ||
@@ -650,3 +750,5 @@ const argv = process.argv.slice(2); | ||
| } | ||
| const mode = resolveMode(parsed, process.stdout.isTTY ?? false); | ||
| const isTTY = process.stdout.isTTY ?? false; | ||
| const mode = resolveMode(parsed, isTTY); | ||
| const updateCheck = isTTY ? checkForUpdate(name, version) : null; | ||
| try { | ||
@@ -656,2 +758,4 @@ const { config } = await loadGreenlyConfig(); | ||
| process.exitCode = exitCode; | ||
| const update = updateCheck ? await updateCheck : null; | ||
| if (update) printUpdateNotice(update); | ||
| } catch (error) { | ||
@@ -658,0 +762,0 @@ if (error instanceof ConfigNotFoundError || error instanceof ConfigInvalidError) { |
+6
-6
@@ -5,3 +5,3 @@ //#region src/lib/types.d.ts | ||
| */ | ||
| interface OnFailContext { | ||
| type OnFailContext = { | ||
| /** The check that failed. */ | ||
@@ -11,3 +11,3 @@ check: GreenlyCheck; | ||
| error: unknown; | ||
| } | ||
| }; | ||
| /** | ||
@@ -27,3 +27,3 @@ * A function run to fix a failing check. Invoked after the user confirms | ||
| */ | ||
| interface GreenlyCheck { | ||
| type GreenlyCheck = { | ||
| /** Label shown while running and in the final summary, e.g. "TypeScript". */ | ||
@@ -48,3 +48,3 @@ name: string; | ||
| optional?: boolean; | ||
| } | ||
| }; | ||
| /** | ||
@@ -54,3 +54,3 @@ * Greenly configuration. Author it with {@link defineConfig} in a | ||
| */ | ||
| interface GreenlyConfig { | ||
| type GreenlyConfig = { | ||
| /** Project name shown in the banner. Defaults to the package name / cwd. */ | ||
@@ -60,3 +60,3 @@ name?: string; | ||
| checks: GreenlyCheck[]; | ||
| } | ||
| }; | ||
| //#endregion | ||
@@ -63,0 +63,0 @@ //#region src/lib/define-config.d.ts |
+6
-6
@@ -5,3 +5,3 @@ //#region src/lib/types.d.ts | ||
| */ | ||
| interface OnFailContext { | ||
| type OnFailContext = { | ||
| /** The check that failed. */ | ||
@@ -11,3 +11,3 @@ check: GreenlyCheck; | ||
| error: unknown; | ||
| } | ||
| }; | ||
| /** | ||
@@ -27,3 +27,3 @@ * A function run to fix a failing check. Invoked after the user confirms | ||
| */ | ||
| interface GreenlyCheck { | ||
| type GreenlyCheck = { | ||
| /** Label shown while running and in the final summary, e.g. "TypeScript". */ | ||
@@ -48,3 +48,3 @@ name: string; | ||
| optional?: boolean; | ||
| } | ||
| }; | ||
| /** | ||
@@ -54,3 +54,3 @@ * Greenly configuration. Author it with {@link defineConfig} in a | ||
| */ | ||
| interface GreenlyConfig { | ||
| type GreenlyConfig = { | ||
| /** Project name shown in the banner. Defaults to the package name / cwd. */ | ||
@@ -60,3 +60,3 @@ name?: string; | ||
| checks: GreenlyCheck[]; | ||
| } | ||
| }; | ||
| //#endregion | ||
@@ -63,0 +63,0 @@ //#region src/lib/define-config.d.ts |
+1
-1
| { | ||
| "name": "greenly", | ||
| "version": "1.1.0", | ||
| "version": "1.1.1", | ||
| "description": "Config-driven project check runner. Define your lint/format/typecheck/test steps in greenly.config.ts and run them with one command.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
+57
-16
@@ -1,2 +0,5 @@ | ||
| # greenly | ||
| <h1> | ||
| <img src="https://raw.githubusercontent.com/yusifaliyevpro/greenly/main/assets/greenly.svg" alt="" height="34" align="top" /> | ||
| greenly | ||
| </h1> | ||
@@ -10,13 +13,20 @@ [](https://www.npmjs.com/package/greenly) | ||
| > Config-driven project check runner. Define your lint / format / typecheck / test steps once in `greenly.config.ts` and run them with a single command. | ||
| > Config-driven project check runner. Define your lint / format / typecheck / test / custom steps once in `greenly.config.ts` and run them with a single command. | ||
| Stop copy-pasting a `pr-checks` script into every repo. Describe your checks in a typed config, and `greenly` runs them in order, streams their output, and offers to auto-fix the ones that can be fixed. | ||
| ## Why greenly | ||
| **greenly runs all your lint, format, typecheck, test, build, and custom checks from one config file with a single command.** | ||
| Instead of pushing and waiting for CI to catch a formatting slip or a type error, run | ||
| `pnpm greenly` before you open a PR. It puts your CI checks and local checks in the same | ||
| place, so if everything is green locally, it is green in CI. Checks run in order with | ||
| their output streamed live, and greenly offers to auto-fix the ones that have a fixer. | ||
| It works where your tools do. In an interactive terminal greenly prompts before running a | ||
| fixer; in an **agent terminal or CI (non-TTY)** it skips prompts and just reports pass or | ||
| fail, so it never hangs and an agent can run `greenly` directly. | ||
| ## Quick start | ||
| The fastest way to get going is the interactive scaffolder. It asks for a project | ||
| name, config format, script name, and which checks to include, then writes the config, | ||
| adds a `"check": "greenly"` script, and installs greenly. It adapts to your project: | ||
| it reuses matching `package.json` scripts (e.g. `fmt:check`, `lint`) when they exist, | ||
| only offers the lint/format tools you already use, and detects Next.js: | ||
| Run this and greenly sets everything up for you, based on the tools your project already uses: | ||
@@ -75,2 +85,33 @@ ```bash | ||
| ## Use it in CI | ||
| The same command you run locally is the command CI runs. Replace your separate check | ||
| steps with one: | ||
| ```diff | ||
| - run: pnpm install --frozen-lockfile | ||
| - - name: TypeScript | ||
| - run: pnpm tsc --noEmit | ||
| - | ||
| - - name: Oxfmt | ||
| - run: pnpm fmt:check | ||
| - | ||
| - - name: Oxlint | ||
| - run: pnpm lint | ||
| - | ||
| - - name: Tests | ||
| - run: pnpm test | ||
| - | ||
| - - name: Build | ||
| - run: pnpm build | ||
| - | ||
| - - name: Version | ||
| - run: node --no-warnings scripts/version-check.ts | ||
| - | ||
| + # pnpm greenly | ||
| + - name: Run checks | ||
| + run: pnpm check | ||
| ``` | ||
| ## Config reference | ||
@@ -131,10 +172,10 @@ | ||
| | Command / Flag | Description | | ||
| | ---------------------- | ---------------------------------------------------------------------------------- | | ||
| | `greenly` | Run the checks from `greenly.config.*`. | | ||
| | `greenly init` | Scaffold a config interactively (format, script name, checks) and install greenly. | | ||
| | `-y`, `--yes`, `--fix` | Auto-run every `onFail` fixer without prompting (great for CI / agents). | | ||
| | `--no-fix` | Run all checks, never prompt or fix, just report. | | ||
| | `-v`, `--version` | Print the version. | | ||
| | `-h`, `--help` | Show help. | | ||
| | Command / Flag | Description | | ||
| | ---------------------- | ------------------------------------------------------------------------ | | ||
| | `greenly` | Run the checks from `greenly.config.*`. | | ||
| | `greenly init` | Set up a config by answering a few questions, then install greenly. | | ||
| | `-y`, `--yes`, `--fix` | Auto-run every `onFail` fixer without prompting (great for CI / agents). | | ||
| | `--no-fix` | Run all checks, never prompt or fix, just report. | | ||
| | `-v`, `--version` | Print the version. | | ||
| | `-h`, `--help` | Show help. | | ||
@@ -141,0 +182,0 @@ When stdout is **not a TTY** (CI, piped output), greenly is non-interactive by default, so it never prompts and nothing hangs. Use `--yes` there to auto-apply fixes. |
Network access
Supply chain riskThis module accesses the network.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
39264
11.61%788
15.2%184
28.67%7
75%2
100%