@viewlint/create-config 0.0.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/README.md +25 -0
- package/bin/create-config.js +12 -0
- package/dist/applySetupPlan.d.ts +22 -0
- package/dist/applySetupPlan.d.ts.map +1 -0
- package/dist/applySetupPlan.js +75 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/installDependencies.d.ts +31 -0
- package/dist/installDependencies.d.ts.map +1 -0
- package/dist/installDependencies.js +62 -0
- package/dist/packageJson.d.ts +26 -0
- package/dist/packageJson.d.ts.map +1 -0
- package/dist/packageJson.js +51 -0
- package/dist/promptForSetupPlan.d.ts +16 -0
- package/dist/promptForSetupPlan.d.ts.map +1 -0
- package/dist/promptForSetupPlan.js +173 -0
- package/dist/viewlintConfigFile.d.ts +25 -0
- package/dist/viewlintConfigFile.d.ts.map +1 -0
- package/dist/viewlintConfigFile.js +61 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @viewlint/create-config
|
|
2
|
+
|
|
3
|
+
Interactive initializer for ViewLint configuration.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @viewlint/create-config
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm init @viewlint/config
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
- Asks which preset to use (`@viewlint/rules` recommended vs all)
|
|
20
|
+
- Asks whether to generate a TypeScript or JavaScript config
|
|
21
|
+
- Writes a `viewlint.config.ts` or `viewlint.config.mjs` file
|
|
22
|
+
- Optionally installs required dependencies as dev dependencies
|
|
23
|
+
- If you choose to install dependencies and no `package.json` exists, it can create a minimal one
|
|
24
|
+
|
|
25
|
+
For more info, see the [docs](https://viewlint.vercel.app/docs/getting-started)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { run } from "../dist/index.js"
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const exitCode = await run(process.argv)
|
|
7
|
+
process.exitCode = exitCode
|
|
8
|
+
} catch (error) {
|
|
9
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
10
|
+
process.stderr.write(`${message}\n`)
|
|
11
|
+
process.exitCode = 1
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SpawnSyncLike } from "./installDependencies.js";
|
|
2
|
+
import type { SetupPlan } from "./promptForSetupPlan.js";
|
|
3
|
+
export type ApplySetupPlanResult = {
|
|
4
|
+
exitCode: number;
|
|
5
|
+
configFilePath: string | null;
|
|
6
|
+
existingConfigFilePath: string | null;
|
|
7
|
+
createdPackageJsonPath: string | null;
|
|
8
|
+
installed: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function applySetupPlan(opts: {
|
|
11
|
+
cwd: string;
|
|
12
|
+
plan: SetupPlan;
|
|
13
|
+
runtime?: {
|
|
14
|
+
platform?: NodeJS.Platform;
|
|
15
|
+
spawnSync?: SpawnSyncLike;
|
|
16
|
+
stat?: (filePath: string) => Promise<{
|
|
17
|
+
isFile(): boolean;
|
|
18
|
+
}>;
|
|
19
|
+
writeFile?: (filePath: string, contents: string, encoding: "utf8") => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
}): Promise<ApplySetupPlanResult>;
|
|
22
|
+
//# sourceMappingURL=applySetupPlan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applySetupPlan.d.ts","sourceRoot":"","sources":["../src/applySetupPlan.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,aAAa,EAClB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAMxD,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,SAAS,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;QAC1B,SAAS,CAAC,EAAE,aAAa,CAAA;QACzB,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,MAAM,IAAI,OAAO,CAAA;SAAE,CAAC,CAAA;QAC3D,SAAS,CAAC,EAAE,CACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,IAAI,CAAC,CAAA;KAClB,CAAA;CACD,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA8EhC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { installDependencies, } from "./installDependencies.js";
|
|
2
|
+
import { writePackageJsonIfMissing } from "./packageJson.js";
|
|
3
|
+
import { findExistingViewlintConfigFile, writeViewlintConfigFile, } from "./viewlintConfigFile.js";
|
|
4
|
+
export async function applySetupPlan(opts) {
|
|
5
|
+
const existingConfigFilePath = await findExistingViewlintConfigFile({
|
|
6
|
+
cwd: opts.cwd,
|
|
7
|
+
runtime: opts.runtime ? { stat: opts.runtime.stat } : undefined,
|
|
8
|
+
});
|
|
9
|
+
if (existingConfigFilePath) {
|
|
10
|
+
return {
|
|
11
|
+
exitCode: 1,
|
|
12
|
+
configFilePath: null,
|
|
13
|
+
existingConfigFilePath,
|
|
14
|
+
createdPackageJsonPath: null,
|
|
15
|
+
installed: false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const configFilePath = await writeViewlintConfigFile({
|
|
19
|
+
cwd: opts.cwd,
|
|
20
|
+
preset: opts.plan.preset,
|
|
21
|
+
language: opts.plan.language,
|
|
22
|
+
runtime: opts.runtime ? { writeFile: opts.runtime.writeFile } : undefined,
|
|
23
|
+
});
|
|
24
|
+
if (!opts.plan.installNow) {
|
|
25
|
+
return {
|
|
26
|
+
exitCode: 0,
|
|
27
|
+
configFilePath,
|
|
28
|
+
existingConfigFilePath: null,
|
|
29
|
+
createdPackageJsonPath: null,
|
|
30
|
+
installed: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
let createdPackageJsonPath = null;
|
|
34
|
+
if (opts.plan.createPackageJson) {
|
|
35
|
+
const created = await writePackageJsonIfMissing({
|
|
36
|
+
cwd: opts.cwd,
|
|
37
|
+
runtime: opts.runtime
|
|
38
|
+
? {
|
|
39
|
+
stat: opts.runtime.stat,
|
|
40
|
+
writeFile: opts.runtime.writeFile,
|
|
41
|
+
}
|
|
42
|
+
: undefined,
|
|
43
|
+
});
|
|
44
|
+
createdPackageJsonPath = created?.filePath ?? null;
|
|
45
|
+
}
|
|
46
|
+
const packageManager = opts.plan.packageManager;
|
|
47
|
+
if (!packageManager) {
|
|
48
|
+
return {
|
|
49
|
+
exitCode: 1,
|
|
50
|
+
configFilePath,
|
|
51
|
+
existingConfigFilePath: null,
|
|
52
|
+
createdPackageJsonPath,
|
|
53
|
+
installed: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const installExitCode = await installDependencies({
|
|
57
|
+
cwd: opts.cwd,
|
|
58
|
+
packageManager,
|
|
59
|
+
dependencies: opts.plan.dependencies,
|
|
60
|
+
runtime: opts.runtime
|
|
61
|
+
? {
|
|
62
|
+
platform: opts.runtime.platform,
|
|
63
|
+
spawnSync: opts.runtime.spawnSync,
|
|
64
|
+
stat: opts.runtime.stat,
|
|
65
|
+
}
|
|
66
|
+
: undefined,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
exitCode: installExitCode,
|
|
70
|
+
configFilePath,
|
|
71
|
+
existingConfigFilePath: null,
|
|
72
|
+
createdPackageJsonPath,
|
|
73
|
+
installed: installExitCode === 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SetupPlan } from "./promptForSetupPlan.js";
|
|
2
|
+
type PromptForSetupPlan = (opts: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
}) => Promise<SetupPlan | null>;
|
|
5
|
+
export declare function run(_argv: string[]): Promise<number>;
|
|
6
|
+
export declare function runInternal(opts: {
|
|
7
|
+
cwd: string;
|
|
8
|
+
prompt: PromptForSetupPlan;
|
|
9
|
+
}): Promise<number>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAIxD,KAAK,kBAAkB,GAAG,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAA;AAE9E,wBAAsB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAM1D;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,kBAAkB,CAAA;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAqClB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { applySetupPlan } from "./applySetupPlan.js";
|
|
2
|
+
import { promptForSetupPlan } from "./promptForSetupPlan.js";
|
|
3
|
+
import { findExistingViewlintConfigFile } from "./viewlintConfigFile.js";
|
|
4
|
+
export async function run(_argv) {
|
|
5
|
+
// argv reserved for future non-interactive flags
|
|
6
|
+
return runInternal({
|
|
7
|
+
cwd: process.cwd(),
|
|
8
|
+
prompt: promptForSetupPlan,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export async function runInternal(opts) {
|
|
12
|
+
const existingConfigFile = await findExistingViewlintConfigFile({
|
|
13
|
+
cwd: opts.cwd,
|
|
14
|
+
});
|
|
15
|
+
if (existingConfigFile) {
|
|
16
|
+
process.stderr.write(`A ViewLint config file already exists at ${existingConfigFile}. Remove it and re-run this initializer.\n`);
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
const plan = await opts.prompt({ cwd: opts.cwd });
|
|
20
|
+
if (plan === null)
|
|
21
|
+
return 0;
|
|
22
|
+
const result = await applySetupPlan({ cwd: opts.cwd, plan });
|
|
23
|
+
if (result.existingConfigFilePath) {
|
|
24
|
+
process.stderr.write(`A ViewLint config file already exists at ${result.existingConfigFilePath}. Remove it and re-run this initializer.\n`);
|
|
25
|
+
return result.exitCode;
|
|
26
|
+
}
|
|
27
|
+
if (result.createdPackageJsonPath) {
|
|
28
|
+
process.stdout.write(`Created ${result.createdPackageJsonPath}\n`);
|
|
29
|
+
}
|
|
30
|
+
if (result.configFilePath) {
|
|
31
|
+
process.stdout.write(`Created ${result.configFilePath}\n`);
|
|
32
|
+
}
|
|
33
|
+
if (!plan.installNow) {
|
|
34
|
+
process.stdout.write(`Next: install ${plan.dependencies.join(", ")} and run viewlint.\n`);
|
|
35
|
+
}
|
|
36
|
+
return result.exitCode;
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PackageManager } from "./promptForSetupPlan.js";
|
|
2
|
+
type InstallCommand = {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
};
|
|
6
|
+
export type SpawnSyncLike = (command: string, args: string[], options: {
|
|
7
|
+
cwd: string;
|
|
8
|
+
encoding: "utf8";
|
|
9
|
+
stdio: "inherit";
|
|
10
|
+
}) => {
|
|
11
|
+
status: number | null;
|
|
12
|
+
error?: unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function getInstallCommand(opts: {
|
|
15
|
+
packageManager: PackageManager;
|
|
16
|
+
dependencies: readonly string[];
|
|
17
|
+
}): InstallCommand;
|
|
18
|
+
export declare function installDependencies(opts: {
|
|
19
|
+
cwd: string;
|
|
20
|
+
packageManager: PackageManager;
|
|
21
|
+
dependencies: readonly string[];
|
|
22
|
+
runtime?: {
|
|
23
|
+
platform?: NodeJS.Platform;
|
|
24
|
+
spawnSync?: SpawnSyncLike;
|
|
25
|
+
stat?: (filePath: string) => Promise<{
|
|
26
|
+
isFile(): boolean;
|
|
27
|
+
}>;
|
|
28
|
+
};
|
|
29
|
+
}): Promise<number>;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=installDependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installDependencies.d.ts","sourceRoot":"","sources":["../src/installDependencies.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAE7D,KAAK,cAAc,GAAG;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;CACd,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACR,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,SAAS,CAAA;CAChB,KACG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAU/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACvC,cAAc,EAAE,cAAc,CAAA;IAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B,GAAG,cAAc,CAqBjB;AAoBD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAA;IACX,cAAc,EAAE,cAAc,CAAA;IAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,OAAO,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;QAC1B,SAAS,CAAC,EAAE,aAAa,CAAA;QACzB,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,MAAM,IAAI,OAAO,CAAA;SAAE,CAAC,CAAA;KAC3D,CAAA;CACD,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function getPlatformCommand(command, platform) {
|
|
5
|
+
if (platform !== "win32")
|
|
6
|
+
return command;
|
|
7
|
+
return `${command}.cmd`;
|
|
8
|
+
}
|
|
9
|
+
export function getInstallCommand(opts) {
|
|
10
|
+
const deps = [...opts.dependencies];
|
|
11
|
+
if (opts.packageManager === "npm") {
|
|
12
|
+
return { command: "npm", args: ["install", "--save-dev", ...deps] };
|
|
13
|
+
}
|
|
14
|
+
if (opts.packageManager === "yarn") {
|
|
15
|
+
return { command: "yarn", args: ["add", "--dev", ...deps] };
|
|
16
|
+
}
|
|
17
|
+
if (opts.packageManager === "pnpm") {
|
|
18
|
+
return { command: "pnpm", args: ["add", "--save-dev", ...deps] };
|
|
19
|
+
}
|
|
20
|
+
if (opts.packageManager === "bun") {
|
|
21
|
+
return { command: "bun", args: ["add", "--dev", ...deps] };
|
|
22
|
+
}
|
|
23
|
+
const _exhaustive = opts.packageManager;
|
|
24
|
+
throw new Error(`Unsupported package manager: ${_exhaustive}`);
|
|
25
|
+
}
|
|
26
|
+
async function assertHasPackageJson(cwd, stat) {
|
|
27
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
28
|
+
try {
|
|
29
|
+
const stats = await stat(packageJsonPath);
|
|
30
|
+
if (!stats.isFile()) {
|
|
31
|
+
throw new Error(`Expected ${packageJsonPath} to be a file.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
+
throw new Error(`Cannot install dependencies because no package.json was found at ${packageJsonPath}. (${message})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function installDependencies(opts) {
|
|
40
|
+
const platform = opts.runtime?.platform ?? process.platform;
|
|
41
|
+
const spawn = opts.runtime?.spawnSync ?? spawnSync;
|
|
42
|
+
const stat = opts.runtime?.stat ?? fs.stat;
|
|
43
|
+
await assertHasPackageJson(opts.cwd, stat);
|
|
44
|
+
const install = getInstallCommand({
|
|
45
|
+
packageManager: opts.packageManager,
|
|
46
|
+
dependencies: opts.dependencies,
|
|
47
|
+
});
|
|
48
|
+
process.stdout.write(`Installing dev dependencies using ${opts.packageManager}: ${opts.dependencies.join(", ")}\n`);
|
|
49
|
+
const result = spawn(getPlatformCommand(install.command, platform), install.args, {
|
|
50
|
+
cwd: opts.cwd,
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
stdio: "inherit",
|
|
53
|
+
});
|
|
54
|
+
if (result.error) {
|
|
55
|
+
const message = result.error instanceof Error
|
|
56
|
+
? result.error.message
|
|
57
|
+
: String(result.error);
|
|
58
|
+
process.stderr.write(`${message}\n`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
return result.status ?? 1;
|
|
62
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type PackageJson = {
|
|
2
|
+
name: string;
|
|
3
|
+
private: true;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function derivePackageNameFromCwd(cwd: string): string;
|
|
7
|
+
export declare function renderPackageJson(opts: {
|
|
8
|
+
cwd: string;
|
|
9
|
+
}): {
|
|
10
|
+
filePath: string;
|
|
11
|
+
contents: string;
|
|
12
|
+
packageName: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function writePackageJsonIfMissing(opts: {
|
|
15
|
+
cwd: string;
|
|
16
|
+
runtime?: {
|
|
17
|
+
stat?: (filePath: string) => Promise<{
|
|
18
|
+
isFile(): boolean;
|
|
19
|
+
}>;
|
|
20
|
+
writeFile?: (filePath: string, contents: string, encoding: "utf8") => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
}): Promise<{
|
|
23
|
+
filePath: string;
|
|
24
|
+
packageName: string;
|
|
25
|
+
} | null>;
|
|
26
|
+
//# sourceMappingURL=packageJson.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packageJson.d.ts","sourceRoot":"","sources":["../src/packageJson.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,IAAI,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CACf,CAAA;AAuBD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IACzD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACnB,CAeA;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACrD,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,MAAM,IAAI,OAAO,CAAA;SAAE,CAAC,CAAA;QAC3D,SAAS,CAAC,EAAE,CACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,IAAI,CAAC,CAAA;KAClB,CAAA;CACD,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAkB5D"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function sanitizePackageName(name) {
|
|
4
|
+
const lower = name.trim().toLowerCase();
|
|
5
|
+
const cleaned = lower
|
|
6
|
+
.replace(/\s+/g, "-")
|
|
7
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
8
|
+
.replace(/-+/g, "-")
|
|
9
|
+
.replace(/^-+/, "")
|
|
10
|
+
.replace(/-+$/, "");
|
|
11
|
+
const withoutForbiddenPrefix = cleaned.replace(/^[._-]+/, "");
|
|
12
|
+
if (withoutForbiddenPrefix.length === 0) {
|
|
13
|
+
throw new Error(`Cannot derive a valid package name from '${name}'. Create a package.json manually and re-run.`);
|
|
14
|
+
}
|
|
15
|
+
return withoutForbiddenPrefix;
|
|
16
|
+
}
|
|
17
|
+
export function derivePackageNameFromCwd(cwd) {
|
|
18
|
+
const baseName = path.basename(cwd);
|
|
19
|
+
return sanitizePackageName(baseName);
|
|
20
|
+
}
|
|
21
|
+
export function renderPackageJson(opts) {
|
|
22
|
+
const packageName = derivePackageNameFromCwd(opts.cwd);
|
|
23
|
+
const filePath = path.join(opts.cwd, "package.json");
|
|
24
|
+
const pkg = {
|
|
25
|
+
name: packageName,
|
|
26
|
+
private: true,
|
|
27
|
+
version: "0.0.0",
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
filePath,
|
|
31
|
+
contents: `${JSON.stringify(pkg, null, 2)}\n`,
|
|
32
|
+
packageName,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function writePackageJsonIfMissing(opts) {
|
|
36
|
+
const stat = opts.runtime?.stat ?? fs.stat;
|
|
37
|
+
const writeFile = opts.runtime?.writeFile ?? fs.writeFile;
|
|
38
|
+
const rendered = renderPackageJson({ cwd: opts.cwd });
|
|
39
|
+
try {
|
|
40
|
+
const stats = await stat(rendered.filePath);
|
|
41
|
+
if (!stats.isFile()) {
|
|
42
|
+
throw new Error(`Expected ${rendered.filePath} to be a file.`);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Missing: create it.
|
|
48
|
+
}
|
|
49
|
+
await writeFile(rendered.filePath, rendered.contents, "utf8");
|
|
50
|
+
return { filePath: rendered.filePath, packageName: rendered.packageName };
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ConfigPreset = "recommended" | "all";
|
|
2
|
+
export type ConfigLanguage = "typescript" | "javascript";
|
|
3
|
+
export type PackageManager = "npm" | "yarn" | "pnpm" | "bun";
|
|
4
|
+
export type SetupPlan = {
|
|
5
|
+
preset: ConfigPreset;
|
|
6
|
+
language: ConfigLanguage;
|
|
7
|
+
dependencies: readonly string[];
|
|
8
|
+
installNow: boolean;
|
|
9
|
+
createPackageJson: boolean;
|
|
10
|
+
packageManager: PackageManager | null;
|
|
11
|
+
};
|
|
12
|
+
export declare function getRequiredDependencies(): readonly string[];
|
|
13
|
+
export declare function promptForSetupPlan(opts: {
|
|
14
|
+
cwd: string;
|
|
15
|
+
}): Promise<SetupPlan | null>;
|
|
16
|
+
//# sourceMappingURL=promptForSetupPlan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptForSetupPlan.d.ts","sourceRoot":"","sources":["../src/promptForSetupPlan.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,KAAK,CAAA;AAChD,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,YAAY,CAAA;AACxD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAE5D,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,cAAc,CAAA;IACxB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,UAAU,EAAE,OAAO,CAAA;IACnB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAA;CACrC,CAAA;AAID,wBAAgB,uBAAuB,IAAI,SAAS,MAAM,EAAE,CAE3D;AAkDD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAA;CACX,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAiJ5B"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
const REQUIRED_DEPENDENCIES = ["viewlint", "@viewlint/rules"];
|
|
5
|
+
export function getRequiredDependencies() {
|
|
6
|
+
return [...REQUIRED_DEPENDENCIES];
|
|
7
|
+
}
|
|
8
|
+
function parseConfigPreset(value) {
|
|
9
|
+
if (value === "recommended" || value === "all")
|
|
10
|
+
return value;
|
|
11
|
+
throw new Error(`Unexpected preset selection: ${String(value)}`);
|
|
12
|
+
}
|
|
13
|
+
function parseConfigLanguage(value) {
|
|
14
|
+
if (value === "typescript" || value === "javascript")
|
|
15
|
+
return value;
|
|
16
|
+
throw new Error(`Unexpected language selection: ${String(value)}`);
|
|
17
|
+
}
|
|
18
|
+
function parsePackageManager(value) {
|
|
19
|
+
if (value === "npm" ||
|
|
20
|
+
value === "yarn" ||
|
|
21
|
+
value === "pnpm" ||
|
|
22
|
+
value === "bun") {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Unexpected package manager selection: ${String(value)}`);
|
|
26
|
+
}
|
|
27
|
+
function parseConfirm(value) {
|
|
28
|
+
if (value === true || value === false)
|
|
29
|
+
return value;
|
|
30
|
+
throw new Error(`Unexpected confirmation value: ${String(value)}`);
|
|
31
|
+
}
|
|
32
|
+
function detectPackageManagerFromLockfiles(cwd) {
|
|
33
|
+
const candidates = [
|
|
34
|
+
{ pm: "bun", file: "bun.lockb" },
|
|
35
|
+
{ pm: "bun", file: "bun.lock" },
|
|
36
|
+
{ pm: "pnpm", file: "pnpm-lock.yaml" },
|
|
37
|
+
{ pm: "yarn", file: "yarn.lock" },
|
|
38
|
+
{ pm: "npm", file: "package-lock.json" },
|
|
39
|
+
{ pm: "npm", file: "npm-shrinkwrap.json" },
|
|
40
|
+
];
|
|
41
|
+
for (const { pm, file } of candidates) {
|
|
42
|
+
if (fs.existsSync(path.join(cwd, file)))
|
|
43
|
+
return pm;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function formatDependencyList(deps) {
|
|
48
|
+
return deps.join(", ");
|
|
49
|
+
}
|
|
50
|
+
export async function promptForSetupPlan(opts) {
|
|
51
|
+
p.intro("ViewLint config");
|
|
52
|
+
const presetRaw = await p.select({
|
|
53
|
+
message: "How would you like to configure ViewLint?",
|
|
54
|
+
options: [
|
|
55
|
+
{
|
|
56
|
+
value: "recommended",
|
|
57
|
+
label: "Base rules (Recommended)",
|
|
58
|
+
hint: "@viewlint/rules:recommended",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
value: "all",
|
|
62
|
+
label: "All rules (Best for AI Agents)",
|
|
63
|
+
hint: "@viewlint/rules:all",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
initialValue: "recommended",
|
|
67
|
+
});
|
|
68
|
+
if (p.isCancel(presetRaw)) {
|
|
69
|
+
p.cancel("Setup cancelled.");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const preset = parseConfigPreset(presetRaw);
|
|
73
|
+
const languageRaw = await p.select({
|
|
74
|
+
message: "What language do you want your configuration file to be written in?",
|
|
75
|
+
options: [
|
|
76
|
+
{ value: "typescript", label: "TypeScript" },
|
|
77
|
+
{ value: "javascript", label: "JavaScript" },
|
|
78
|
+
],
|
|
79
|
+
initialValue: "typescript",
|
|
80
|
+
});
|
|
81
|
+
if (p.isCancel(languageRaw)) {
|
|
82
|
+
p.cancel("Setup cancelled.");
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const language = parseConfigLanguage(languageRaw);
|
|
86
|
+
const dependencies = getRequiredDependencies();
|
|
87
|
+
const installNowRaw = await p.confirm({
|
|
88
|
+
message: `The config you've selected requires the following dependencies: ${formatDependencyList(dependencies)}. Would you like to install them now?`,
|
|
89
|
+
initialValue: true,
|
|
90
|
+
});
|
|
91
|
+
if (p.isCancel(installNowRaw)) {
|
|
92
|
+
p.cancel("Setup cancelled.");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const installNow = parseConfirm(installNowRaw);
|
|
96
|
+
if (!installNow) {
|
|
97
|
+
p.outro("Config selection complete.");
|
|
98
|
+
return {
|
|
99
|
+
preset,
|
|
100
|
+
language,
|
|
101
|
+
dependencies,
|
|
102
|
+
installNow,
|
|
103
|
+
createPackageJson: false,
|
|
104
|
+
packageManager: null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const packageJsonPath = path.join(opts.cwd, "package.json");
|
|
108
|
+
const hasPackageJson = fs.existsSync(packageJsonPath);
|
|
109
|
+
let createPackageJson = false;
|
|
110
|
+
if (!hasPackageJson) {
|
|
111
|
+
const createPackageJsonRaw = await p.confirm({
|
|
112
|
+
message: "No package.json was found in this directory. Create one now? (required to install dependencies)",
|
|
113
|
+
initialValue: true,
|
|
114
|
+
});
|
|
115
|
+
if (p.isCancel(createPackageJsonRaw)) {
|
|
116
|
+
p.cancel("Setup cancelled.");
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
createPackageJson = parseConfirm(createPackageJsonRaw);
|
|
120
|
+
if (!createPackageJson) {
|
|
121
|
+
p.outro("Config selection complete.");
|
|
122
|
+
return {
|
|
123
|
+
preset,
|
|
124
|
+
language,
|
|
125
|
+
dependencies,
|
|
126
|
+
installNow: false,
|
|
127
|
+
createPackageJson: false,
|
|
128
|
+
packageManager: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const detectedPm = detectPackageManagerFromLockfiles(opts.cwd);
|
|
133
|
+
const packageManagerRaw = await p.select({
|
|
134
|
+
message: "Which package manager would you like to use?",
|
|
135
|
+
options: [
|
|
136
|
+
{
|
|
137
|
+
value: "npm",
|
|
138
|
+
label: "npm",
|
|
139
|
+
hint: detectedPm === "npm" ? "detected" : undefined,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
value: "yarn",
|
|
143
|
+
label: "yarn",
|
|
144
|
+
hint: detectedPm === "yarn" ? "detected" : undefined,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
value: "pnpm",
|
|
148
|
+
label: "pnpm",
|
|
149
|
+
hint: detectedPm === "pnpm" ? "detected" : undefined,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
value: "bun",
|
|
153
|
+
label: "bun",
|
|
154
|
+
hint: detectedPm === "bun" ? "detected" : undefined,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
initialValue: detectedPm ?? "npm",
|
|
158
|
+
});
|
|
159
|
+
if (p.isCancel(packageManagerRaw)) {
|
|
160
|
+
p.cancel("Setup cancelled.");
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const packageManager = parsePackageManager(packageManagerRaw);
|
|
164
|
+
p.outro("Config selection complete.");
|
|
165
|
+
return {
|
|
166
|
+
preset,
|
|
167
|
+
language,
|
|
168
|
+
dependencies,
|
|
169
|
+
installNow,
|
|
170
|
+
createPackageJson,
|
|
171
|
+
packageManager,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConfigLanguage, ConfigPreset } from "./promptForSetupPlan.js";
|
|
2
|
+
type ViewLintConfigFileName = "viewlint.config.ts" | "viewlint.config.js" | "viewlint.config.mjs";
|
|
3
|
+
export declare function renderViewlintConfigFile(opts: {
|
|
4
|
+
preset: ConfigPreset;
|
|
5
|
+
language: ConfigLanguage;
|
|
6
|
+
}): {
|
|
7
|
+
fileName: ViewLintConfigFileName;
|
|
8
|
+
contents: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function findExistingViewlintConfigFile(opts: {
|
|
11
|
+
cwd: string;
|
|
12
|
+
runtime?: {
|
|
13
|
+
stat?: (filePath: string) => Promise<unknown>;
|
|
14
|
+
};
|
|
15
|
+
}): Promise<string | null>;
|
|
16
|
+
export declare function writeViewlintConfigFile(opts: {
|
|
17
|
+
cwd: string;
|
|
18
|
+
preset: ConfigPreset;
|
|
19
|
+
language: ConfigLanguage;
|
|
20
|
+
runtime?: {
|
|
21
|
+
writeFile?: (filePath: string, contents: string, encoding: "utf8") => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=viewlintConfigFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewlintConfigFile.d.ts","sourceRoot":"","sources":["../src/viewlintConfigFile.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3E,KAAK,sBAAsB,GACxB,oBAAoB,GACpB,oBAAoB,GACpB,qBAAqB,CAAA;AA0BxB,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,cAAc,CAAA;CACxB,GAAG;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAgBzD;AAED,wBAAsB,8BAA8B,CAAC,IAAI,EAAE;IAC1D,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KAC7C,CAAA;CACD,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAczB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,cAAc,CAAA;IACxB,OAAO,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,CACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,IAAI,CAAC,CAAA;KAClB,CAAA;CACD,GAAG,OAAO,CAAC,MAAM,CAAC,CAWlB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const VIEWLINT_CONFIG_FILE_NAMES = [
|
|
4
|
+
"viewlint.config.ts",
|
|
5
|
+
"viewlint.config.js",
|
|
6
|
+
"viewlint.config.mjs",
|
|
7
|
+
];
|
|
8
|
+
function getTargetConfigFileName(language) {
|
|
9
|
+
if (language === "typescript")
|
|
10
|
+
return "viewlint.config.ts";
|
|
11
|
+
if (language === "javascript")
|
|
12
|
+
return "viewlint.config.mjs";
|
|
13
|
+
const _exhaustive = language;
|
|
14
|
+
throw new Error(`Unsupported config language: ${_exhaustive}`);
|
|
15
|
+
}
|
|
16
|
+
function getExtendsString(preset) {
|
|
17
|
+
if (preset === "recommended")
|
|
18
|
+
return "rules/recommended";
|
|
19
|
+
if (preset === "all")
|
|
20
|
+
return "rules/all";
|
|
21
|
+
const _exhaustive = preset;
|
|
22
|
+
throw new Error(`Unsupported preset: ${_exhaustive}`);
|
|
23
|
+
}
|
|
24
|
+
export function renderViewlintConfigFile(opts) {
|
|
25
|
+
const fileName = getTargetConfigFileName(opts.language);
|
|
26
|
+
const extendsString = getExtendsString(opts.preset);
|
|
27
|
+
const contents = `import { defineConfig } from "viewlint/config"\n` +
|
|
28
|
+
`import rules from "@viewlint/rules"\n` +
|
|
29
|
+
`\n` +
|
|
30
|
+
`export default defineConfig({\n` +
|
|
31
|
+
`\tplugins: {\n` +
|
|
32
|
+
`\t\trules,\n` +
|
|
33
|
+
`\t},\n` +
|
|
34
|
+
`\textends: ["${extendsString}"],\n` +
|
|
35
|
+
`})\n`;
|
|
36
|
+
return { fileName, contents };
|
|
37
|
+
}
|
|
38
|
+
export async function findExistingViewlintConfigFile(opts) {
|
|
39
|
+
const stat = opts.runtime?.stat ?? fs.stat;
|
|
40
|
+
for (const fileName of VIEWLINT_CONFIG_FILE_NAMES) {
|
|
41
|
+
const filePath = path.join(opts.cwd, fileName);
|
|
42
|
+
try {
|
|
43
|
+
await stat(filePath);
|
|
44
|
+
return filePath;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Not found.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
export async function writeViewlintConfigFile(opts) {
|
|
53
|
+
const writeFile = opts.runtime?.writeFile ?? fs.writeFile;
|
|
54
|
+
const rendered = renderViewlintConfigFile({
|
|
55
|
+
preset: opts.preset,
|
|
56
|
+
language: opts.language,
|
|
57
|
+
});
|
|
58
|
+
const targetPath = path.join(opts.cwd, rendered.fileName);
|
|
59
|
+
await writeFile(targetPath, rendered.contents, "utf8");
|
|
60
|
+
return targetPath;
|
|
61
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@viewlint/create-config",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Interactive initializer for ViewLint configuration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": "./bin/create-config.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@clack/prompts": "^1.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@repo/typescript-config": "workspace:*",
|
|
26
|
+
"@types/node": "^22.15.3",
|
|
27
|
+
"bun-types": "^1.3.8",
|
|
28
|
+
"typescript": "5.9.2"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
|
|
32
|
+
"check-types": "tsc -p tsconfig.check.json --noEmit",
|
|
33
|
+
"prepack": "npm run build",
|
|
34
|
+
"test": "bun test"
|
|
35
|
+
}
|
|
36
|
+
}
|