greenly 1.0.1 → 1.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/README.md +31 -9
- package/dist/cli.js +322 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
# greenly
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/greenly)
|
|
4
|
+
[](https://www.npmjs.com/package/greenly)
|
|
5
|
+
[](https://www.npmjs.com/package/greenly)
|
|
6
|
+
[](https://socket.dev/npm/package/greenly)
|
|
7
|
+
[](https://github.com/yusifaliyevpro/greenly/actions/workflows/pr-checks.yml)
|
|
8
|
+
[](https://github.com/yusifaliyevpro/greenly/blob/main/LICENSE)
|
|
9
|
+
|
|
3
10
|
> Config-driven project check runner. Define your lint / format / typecheck / test steps once in `greenly.config.ts` and run them with a single command.
|
|
4
11
|
|
|
5
12
|
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.
|
|
6
13
|
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
The fastest way to get going is the interactive scaffolder. It asks for a project
|
|
17
|
+
name, config format, script name, and which checks to include, then writes the config,
|
|
18
|
+
adds a `"check": "greenly"` script, and installs greenly. It adapts to your project:
|
|
19
|
+
it reuses matching `package.json` scripts (e.g. `fmt:check`, `lint`) when they exist,
|
|
20
|
+
only offers the lint/format tools you already use, and detects Next.js:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpx greenly init
|
|
24
|
+
# or: npx greenly init
|
|
25
|
+
```
|
|
26
|
+
|
|
7
27
|
## Install
|
|
8
28
|
|
|
29
|
+
Or set it up manually:
|
|
30
|
+
|
|
9
31
|
```bash
|
|
10
32
|
pnpm add -D greenly
|
|
11
33
|
```
|
|
12
34
|
|
|
13
|
-
## Quick start
|
|
14
|
-
|
|
15
35
|
Create a `greenly.config.ts` at your project root:
|
|
16
36
|
|
|
17
37
|
```ts
|
|
@@ -104,14 +124,16 @@ greenly.config.js greenly.config.mjs greenly.config.cjs
|
|
|
104
124
|
greenly.config.json
|
|
105
125
|
```
|
|
106
126
|
|
|
107
|
-
## CLI
|
|
127
|
+
## CLI
|
|
108
128
|
|
|
109
|
-
| Flag
|
|
110
|
-
| ---------------------- |
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
| `-
|
|
114
|
-
|
|
|
129
|
+
| Command / Flag | Description |
|
|
130
|
+
| ---------------------- | ---------------------------------------------------------------------------------- |
|
|
131
|
+
| `greenly` | Run the checks from `greenly.config.*`. |
|
|
132
|
+
| `greenly init` | Scaffold a config interactively (format, script name, checks) and install greenly. |
|
|
133
|
+
| `-y`, `--yes`, `--fix` | Auto-run every `onFail` fixer without prompting (great for CI / agents). |
|
|
134
|
+
| `--no-fix` | Run all checks, never prompt or fix, just report. |
|
|
135
|
+
| `-v`, `--version` | Print the version. |
|
|
136
|
+
| `-h`, `--help` | Show help. |
|
|
115
137
|
|
|
116
138
|
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.
|
|
117
139
|
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { createJiti } from "jiti";
|
|
6
|
+
import { exec, execSync } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { cancel, confirm, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
7
9
|
//#region package.json
|
|
8
|
-
var version = "1.0
|
|
10
|
+
var version = "1.1.0";
|
|
9
11
|
//#endregion
|
|
10
12
|
//#region src/lib/args.ts
|
|
11
13
|
function parseArgs(argv) {
|
|
@@ -40,8 +42,7 @@ const colors = {
|
|
|
40
42
|
cyan: wrap(36, 39)
|
|
41
43
|
};
|
|
42
44
|
//#endregion
|
|
43
|
-
//#region src/lib/
|
|
44
|
-
const CONFIG_BASENAME = "greenly.config";
|
|
45
|
+
//#region src/lib/constants.ts
|
|
45
46
|
const CONFIG_EXTENSIONS = [
|
|
46
47
|
"ts",
|
|
47
48
|
"mts",
|
|
@@ -51,6 +52,9 @@ const CONFIG_EXTENSIONS = [
|
|
|
51
52
|
"cjs",
|
|
52
53
|
"json"
|
|
53
54
|
];
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/lib/config.ts
|
|
57
|
+
const CONFIG_BASENAME = "greenly.config";
|
|
54
58
|
var ConfigNotFoundError = class extends Error {
|
|
55
59
|
cwd;
|
|
56
60
|
constructor(cwd) {
|
|
@@ -76,10 +80,7 @@ function findConfigFile(cwd = process.cwd()) {
|
|
|
76
80
|
async function loadGreenlyConfig(cwd = process.cwd()) {
|
|
77
81
|
const configFile = findConfigFile(cwd);
|
|
78
82
|
if (!configFile) throw new ConfigNotFoundError(cwd);
|
|
79
|
-
const
|
|
80
|
-
cwd,
|
|
81
|
-
configFile
|
|
82
|
-
});
|
|
83
|
+
const config = await createJiti(pathToFileURL(resolve(cwd, "greenly.config")).href).import(configFile, { default: true });
|
|
83
84
|
if (!config || typeof config !== "object") throw new ConfigInvalidError(configFile, "config must export an object");
|
|
84
85
|
if (!Array.isArray(config.checks) || config.checks.length === 0) throw new ConfigInvalidError(configFile, `"checks" must be a non-empty array`);
|
|
85
86
|
for (const [i, check] of config.checks.entries()) {
|
|
@@ -92,6 +93,308 @@ async function loadGreenlyConfig(cwd = process.cwd()) {
|
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
//#endregion
|
|
96
|
+
//#region src/lib/init.ts
|
|
97
|
+
const execAsync = promisify(exec);
|
|
98
|
+
function isRecord(value) {
|
|
99
|
+
return typeof value === "object" && value !== null;
|
|
100
|
+
}
|
|
101
|
+
function scriptCommand(ctx, candidates) {
|
|
102
|
+
const found = candidates.find((s) => ctx.scripts.has(s));
|
|
103
|
+
return found ? `${ctx.run} ${found}` : null;
|
|
104
|
+
}
|
|
105
|
+
const CHECK_PRESETS = [
|
|
106
|
+
{
|
|
107
|
+
value: "typescript",
|
|
108
|
+
label: "TypeScript (tsc)",
|
|
109
|
+
build: (c) => ({
|
|
110
|
+
name: "TypeScript",
|
|
111
|
+
command: scriptCommand(c, ["typecheck", "type-check"]) ?? `${c.exec} tsc --noEmit${c.isNext ? " --incremental false" : ""}`
|
|
112
|
+
})
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
value: "oxfmt",
|
|
116
|
+
label: "Oxfmt",
|
|
117
|
+
build: (c) => ({
|
|
118
|
+
name: "Oxfmt",
|
|
119
|
+
command: scriptCommand(c, ["fmt:check", "format:check"]) ?? `${c.exec} oxfmt --check`,
|
|
120
|
+
onFail: scriptCommand(c, ["fmt", "format"]) ?? `${c.exec} oxfmt`
|
|
121
|
+
})
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
value: "prettier",
|
|
125
|
+
label: "Prettier",
|
|
126
|
+
build: (c) => ({
|
|
127
|
+
name: "Prettier",
|
|
128
|
+
command: scriptCommand(c, ["fmt:check", "format:check"]) ?? `${c.exec} prettier --check .`,
|
|
129
|
+
onFail: scriptCommand(c, ["fmt", "format"]) ?? `${c.exec} prettier --write .`
|
|
130
|
+
})
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
value: "oxlint",
|
|
134
|
+
label: "Oxlint",
|
|
135
|
+
build: (c) => ({
|
|
136
|
+
name: "Oxlint",
|
|
137
|
+
command: scriptCommand(c, ["lint"]) ?? `${c.exec} oxlint`
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
value: "eslint",
|
|
142
|
+
label: "ESLint",
|
|
143
|
+
build: (c) => ({
|
|
144
|
+
name: "ESLint",
|
|
145
|
+
command: scriptCommand(c, ["lint"]) ?? `${c.exec} eslint .`
|
|
146
|
+
})
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
value: "vitest",
|
|
150
|
+
label: "Tests (Vitest)",
|
|
151
|
+
build: (c) => ({
|
|
152
|
+
name: "Tests",
|
|
153
|
+
command: scriptCommand(c, ["test"]) ?? `${c.exec} vitest run`
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
value: "build",
|
|
158
|
+
label: "Build",
|
|
159
|
+
build: (c) => ({
|
|
160
|
+
name: "Build",
|
|
161
|
+
command: scriptCommand(c, ["build"]) ?? `${c.run} build`
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
];
|
|
165
|
+
function pmContext(pm) {
|
|
166
|
+
switch (pm) {
|
|
167
|
+
case "pnpm": return {
|
|
168
|
+
exec: "pnpm",
|
|
169
|
+
run: "pnpm"
|
|
170
|
+
};
|
|
171
|
+
case "yarn": return {
|
|
172
|
+
exec: "yarn",
|
|
173
|
+
run: "yarn"
|
|
174
|
+
};
|
|
175
|
+
case "bun": return {
|
|
176
|
+
exec: "bunx",
|
|
177
|
+
run: "bun run"
|
|
178
|
+
};
|
|
179
|
+
default: return {
|
|
180
|
+
exec: "npx",
|
|
181
|
+
run: "npm run"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function installCommand(pm) {
|
|
186
|
+
switch (pm) {
|
|
187
|
+
case "pnpm": return "pnpm add -D greenly@latest";
|
|
188
|
+
case "yarn": return "yarn add -D greenly@latest";
|
|
189
|
+
case "bun": return "bun add -d greenly@latest";
|
|
190
|
+
default: return "npm install -D greenly@latest";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function detectPackageManager(userAgent, lockfiles) {
|
|
194
|
+
const ua = userAgent ?? "";
|
|
195
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
196
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
197
|
+
if (ua.startsWith("bun")) return "bun";
|
|
198
|
+
if (ua.startsWith("npm")) return "npm";
|
|
199
|
+
if (lockfiles.includes("pnpm-lock.yaml")) return "pnpm";
|
|
200
|
+
if (lockfiles.includes("yarn.lock")) return "yarn";
|
|
201
|
+
if (lockfiles.includes("bun.lockb") || lockfiles.includes("bun.lock")) return "bun";
|
|
202
|
+
return "npm";
|
|
203
|
+
}
|
|
204
|
+
function buildChecks(selected, pm, opts = {}) {
|
|
205
|
+
const ctx = {
|
|
206
|
+
...pmContext(pm),
|
|
207
|
+
isNext: opts.isNext ?? false,
|
|
208
|
+
scripts: opts.scripts ?? new Set()
|
|
209
|
+
};
|
|
210
|
+
return CHECK_PRESETS.filter((p) => selected.includes(p.value)).map((p) => p.build(ctx));
|
|
211
|
+
}
|
|
212
|
+
function isNextProject(pkg, hasNextConfig) {
|
|
213
|
+
if (hasNextConfig) return true;
|
|
214
|
+
const hasNextDep = (field) => {
|
|
215
|
+
const deps = pkg?.[field];
|
|
216
|
+
return isRecord(deps) && "next" in deps;
|
|
217
|
+
};
|
|
218
|
+
return hasNextDep("dependencies") || hasNextDep("devDependencies");
|
|
219
|
+
}
|
|
220
|
+
function packageScripts(pkg) {
|
|
221
|
+
const scripts = pkg?.scripts;
|
|
222
|
+
return isRecord(scripts) ? new Set(Object.keys(scripts)) : new Set();
|
|
223
|
+
}
|
|
224
|
+
function installedDependencies(pkg) {
|
|
225
|
+
const names = new Set();
|
|
226
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
227
|
+
const deps = pkg?.[field];
|
|
228
|
+
if (isRecord(deps)) for (const key of Object.keys(deps)) names.add(key);
|
|
229
|
+
}
|
|
230
|
+
return names;
|
|
231
|
+
}
|
|
232
|
+
function availablePresets(installed) {
|
|
233
|
+
const hidden = new Set();
|
|
234
|
+
const prefer = (a, b) => {
|
|
235
|
+
if (installed.has(a) && !installed.has(b)) hidden.add(b);
|
|
236
|
+
if (installed.has(b) && !installed.has(a)) hidden.add(a);
|
|
237
|
+
};
|
|
238
|
+
prefer("oxlint", "eslint");
|
|
239
|
+
prefer("oxfmt", "prettier");
|
|
240
|
+
return CHECK_PRESETS.filter((p) => !hidden.has(p.value));
|
|
241
|
+
}
|
|
242
|
+
function configFileName(ext) {
|
|
243
|
+
return `greenly.config.${ext}`;
|
|
244
|
+
}
|
|
245
|
+
function renderCheck(check) {
|
|
246
|
+
const parts = [`name: ${JSON.stringify(check.name)}`, `command: ${JSON.stringify(check.command)}`];
|
|
247
|
+
if (check.onFail) parts.push(`onFail: ${JSON.stringify(check.onFail)}`);
|
|
248
|
+
return `{ ${parts.join(", ")} }`;
|
|
249
|
+
}
|
|
250
|
+
function isEsm(ext, isModule) {
|
|
251
|
+
if (ext === "ts" || ext === "mts" || ext === "mjs") return true;
|
|
252
|
+
if (ext === "cts" || ext === "cjs") return false;
|
|
253
|
+
return isModule;
|
|
254
|
+
}
|
|
255
|
+
function renderConfig(opts) {
|
|
256
|
+
const { name, checks, ext, isModule } = opts;
|
|
257
|
+
if (ext === "json") return `${JSON.stringify({
|
|
258
|
+
name,
|
|
259
|
+
checks
|
|
260
|
+
}, null, 2)}\n`;
|
|
261
|
+
const body = checks.map((c) => ` ${renderCheck(c)},`).join("\n");
|
|
262
|
+
const object = `{\n name: ${JSON.stringify(name)},\n checks: [\n${body}\n ],\n}`;
|
|
263
|
+
if (isEsm(ext, isModule)) return `import { defineConfig } from "greenly";\n\nexport default defineConfig(${object});\n`;
|
|
264
|
+
return `const { defineConfig } = require("greenly");\n\nmodule.exports = defineConfig(${object});\n`;
|
|
265
|
+
}
|
|
266
|
+
function withCheckScript(pkg, scriptName) {
|
|
267
|
+
const scripts = isRecord(pkg.scripts) ? pkg.scripts : {};
|
|
268
|
+
return {
|
|
269
|
+
...pkg,
|
|
270
|
+
scripts: {
|
|
271
|
+
...scripts,
|
|
272
|
+
[scriptName]: "greenly"
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function readPackageJson(path) {
|
|
277
|
+
if (!existsSync(path)) return null;
|
|
278
|
+
try {
|
|
279
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
280
|
+
if (isRecord(parsed)) return parsed;
|
|
281
|
+
} catch {}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
function detectLockfiles(cwd) {
|
|
285
|
+
return [
|
|
286
|
+
"pnpm-lock.yaml",
|
|
287
|
+
"yarn.lock",
|
|
288
|
+
"package-lock.json",
|
|
289
|
+
"bun.lockb",
|
|
290
|
+
"bun.lock"
|
|
291
|
+
].filter((f) => existsSync(join(cwd, f)));
|
|
292
|
+
}
|
|
293
|
+
function hasNextConfigFile(cwd) {
|
|
294
|
+
return [
|
|
295
|
+
"js",
|
|
296
|
+
"mjs",
|
|
297
|
+
"cjs",
|
|
298
|
+
"ts",
|
|
299
|
+
"mts",
|
|
300
|
+
"cts"
|
|
301
|
+
].some((e) => existsSync(join(cwd, `next.config.${e}`)));
|
|
302
|
+
}
|
|
303
|
+
function ensure(value) {
|
|
304
|
+
if (isCancel(value)) {
|
|
305
|
+
cancel("init cancelled.");
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
async function runInit(cwd = process.cwd()) {
|
|
311
|
+
intro(colors.inverse(" greenly ") + colors.dim(" init "));
|
|
312
|
+
const pkgPath = join(cwd, "package.json");
|
|
313
|
+
const pkg = readPackageJson(pkgPath);
|
|
314
|
+
const defaultName = typeof pkg?.name === "string" ? pkg.name : basename(cwd);
|
|
315
|
+
const pm = detectPackageManager(process.env.npm_config_user_agent, detectLockfiles(cwd));
|
|
316
|
+
const name = ensure(await text({
|
|
317
|
+
message: "Project name (shown in the banner)",
|
|
318
|
+
initialValue: defaultName,
|
|
319
|
+
validate: (value) => value?.trim() ? void 0 : "Please enter a project name"
|
|
320
|
+
}));
|
|
321
|
+
const ext = ensure(await select({
|
|
322
|
+
message: "Config file format",
|
|
323
|
+
options: CONFIG_EXTENSIONS.map((e) => ({
|
|
324
|
+
value: e,
|
|
325
|
+
label: configFileName(e)
|
|
326
|
+
})),
|
|
327
|
+
initialValue: "ts"
|
|
328
|
+
}));
|
|
329
|
+
const scriptName = ensure(await text({
|
|
330
|
+
message: "Script name to add to package.json",
|
|
331
|
+
initialValue: "check",
|
|
332
|
+
validate: (value) => value?.trim() ? void 0 : "Please enter a script name"
|
|
333
|
+
}));
|
|
334
|
+
const presets = availablePresets(installedDependencies(pkg));
|
|
335
|
+
const selected = ensure(await multiselect({
|
|
336
|
+
message: "Select the checks to include",
|
|
337
|
+
options: presets.map((p) => ({
|
|
338
|
+
value: p.value,
|
|
339
|
+
label: p.label
|
|
340
|
+
})),
|
|
341
|
+
required: true
|
|
342
|
+
}));
|
|
343
|
+
const doInstall = ensure(await confirm({
|
|
344
|
+
message: `Install greenly now with ${pm}?`,
|
|
345
|
+
initialValue: true
|
|
346
|
+
}));
|
|
347
|
+
const fileName = configFileName(ext);
|
|
348
|
+
const filePath = join(cwd, fileName);
|
|
349
|
+
if (existsSync(filePath)) {
|
|
350
|
+
if (!ensure(await confirm({
|
|
351
|
+
message: `${fileName} already exists. Overwrite it?`,
|
|
352
|
+
initialValue: false
|
|
353
|
+
}))) {
|
|
354
|
+
cancel("Kept the existing config. Nothing changed.");
|
|
355
|
+
process.exit(0);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const isNext = isNextProject(pkg, hasNextConfigFile(cwd));
|
|
359
|
+
if (isNext && selected.includes("typescript")) log.info(colors.dim("Detected Next.js, using tsc --incremental false"));
|
|
360
|
+
const content = renderConfig({
|
|
361
|
+
name,
|
|
362
|
+
checks: buildChecks(selected, pm, {
|
|
363
|
+
isNext,
|
|
364
|
+
scripts: packageScripts(pkg)
|
|
365
|
+
}),
|
|
366
|
+
ext,
|
|
367
|
+
isModule: pkg?.type === "module"
|
|
368
|
+
});
|
|
369
|
+
writeFileSync(filePath, content);
|
|
370
|
+
log.success(`Created ${colors.bold(fileName)}`);
|
|
371
|
+
if (pkg) {
|
|
372
|
+
const current = (isRecord(pkg.scripts) ? pkg.scripts : {})[scriptName];
|
|
373
|
+
let write = true;
|
|
374
|
+
if (typeof current === "string" && current !== "greenly") write = ensure(await confirm({
|
|
375
|
+
message: `Script "${scriptName}" already runs "${current}". Overwrite with "greenly"?`,
|
|
376
|
+
initialValue: false
|
|
377
|
+
}));
|
|
378
|
+
if (write) {
|
|
379
|
+
writeFileSync(pkgPath, `${JSON.stringify(withCheckScript(pkg, scriptName), null, 2)}\n`);
|
|
380
|
+
log.success(`Added ${colors.bold(`"${scriptName}": "greenly"`)} to package.json`);
|
|
381
|
+
}
|
|
382
|
+
} else log.warn("No package.json found, skipped adding the script.");
|
|
383
|
+
if (doInstall) {
|
|
384
|
+
const s = spinner();
|
|
385
|
+
s.start(`Installing greenly with ${pm}`);
|
|
386
|
+
try {
|
|
387
|
+
await execAsync(installCommand(pm), { cwd });
|
|
388
|
+
s.stop("Installed greenly");
|
|
389
|
+
} catch {
|
|
390
|
+
s.stop("Could not install greenly automatically");
|
|
391
|
+
log.warn(`Run "${installCommand(pm)}" yourself.`);
|
|
392
|
+
}
|
|
393
|
+
} else log.info(`Skipped install. Run "${installCommand(pm)}" when ready.`);
|
|
394
|
+
const runCmd = `${pmContext(pm).run} ${scriptName}`;
|
|
395
|
+
outro(`Done. Run ${colors.bold(runCmd)} to run your checks.`);
|
|
396
|
+
}
|
|
397
|
+
//#endregion
|
|
95
398
|
//#region src/lib/runner.ts
|
|
96
399
|
const MIN_WIDTH = 60;
|
|
97
400
|
function bannerWidth(name) {
|
|
@@ -281,6 +584,7 @@ const HELP = `${colors.bold("greenly")} - config-driven project check runner
|
|
|
281
584
|
|
|
282
585
|
${colors.bold("Usage")}
|
|
283
586
|
greenly [options]
|
|
587
|
+
greenly init Scaffold a greenly.config file interactively
|
|
284
588
|
|
|
285
589
|
${colors.bold("Options")}
|
|
286
590
|
-y, --yes, --fix Auto-run every onFail fixer without prompting (CI / agents)
|
|
@@ -303,7 +607,12 @@ ${colors.bold("Config")}
|
|
|
303
607
|
});
|
|
304
608
|
`;
|
|
305
609
|
async function main() {
|
|
306
|
-
const
|
|
610
|
+
const argv = process.argv.slice(2);
|
|
611
|
+
if (argv[0] === "init") {
|
|
612
|
+
await runInit();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const parsed = parseArgs(argv);
|
|
307
616
|
if (parsed.help) {
|
|
308
617
|
console.log(HELP);
|
|
309
618
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "greenly",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Config-driven project check runner. Define your lint/format/typecheck/test steps in greenly.config.ts and run them with one command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"check",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@clack/prompts": "1.7.0",
|
|
78
|
-
"
|
|
78
|
+
"jiti": "2.7.0"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@types/node": "24.13.3",
|