@amedia/create-kragl 0.0.0-stage → 1.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/CHANGELOG.md +13 -0
- package/README.md +45 -2
- package/bin/create-kragl.js +2 -0
- package/package.json +32 -4
- package/src/cli.js +154 -0
- package/src/plan-test.js +76 -0
- package/src/plan.js +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @amedia/create-kragl
|
|
2
|
+
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 78f91fb: Add the formatting script as `lint:format` instead of `format`
|
|
8
|
+
|
|
9
|
+
## 1.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- 94884aa: Add `@amedia/create-kragl`, which sets up kragl in a project with `npm init @amedia/kragl@latest`: it installs the packages, writes the config files and adds the lint and format scripts.
|
package/README.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @amedia/create-kragl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sets up [kragl](../kragl) in a project:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm init @amedia/kragl@latest
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
In the current directory it:
|
|
10
|
+
|
|
11
|
+
1. Creates a `package.json` if there is none, as an ES module package.
|
|
12
|
+
2. Installs `@amedia/kragl` and `@amedia/kragl-lockfile-lint` as exact
|
|
13
|
+
dev dependencies.
|
|
14
|
+
3. Writes `eslint.config.js` and `prettier.config.js` with `kragl install`.
|
|
15
|
+
4. Adds the `lint`, `lint:fix` and `lint:format` scripts.
|
|
16
|
+
|
|
17
|
+
Running it again only adds what is missing. It never changes an existing
|
|
18
|
+
script or dependency, and keeps existing config files unless `--force` is
|
|
19
|
+
given. Only npm projects are supported: it stops if it finds a Yarn, pnpm or
|
|
20
|
+
Bun lockfile.
|
|
21
|
+
|
|
22
|
+
`@latest` makes npm fetch the newest version instead of a copy installed
|
|
23
|
+
earlier.
|
|
24
|
+
|
|
25
|
+
## Options
|
|
26
|
+
|
|
27
|
+
npm passes options after `--` on to `create-kragl`:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm init @amedia/kragl@latest -- --biome --ls-lint
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Option | Effect |
|
|
34
|
+
| -------------------- | -------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `--biome` | Use [Biome](../kragl-biome): installs `@amedia/kragl-cli` and `@amedia/kragl-biome` instead. |
|
|
36
|
+
| `--no-lockfile-lint` | Leave out [lockfile-lint](../kragl-lockfile-lint). |
|
|
37
|
+
| `--ls-lint` | Add [ls-lint](../kragl-ls-lint). |
|
|
38
|
+
| `--browserslist` | Add [Browserslist](../kragl-browserslist). |
|
|
39
|
+
| `-f, --force` | Overwrite existing config files. |
|
|
40
|
+
|
|
41
|
+
A project that already has `@amedia/kragl-biome` stays on Biome without
|
|
42
|
+
`--biome`.
|
|
43
|
+
|
|
44
|
+
## Notes
|
|
45
|
+
|
|
46
|
+
Requires Node 22+.
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amedia/create-kragl",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Set up kragl in a project with `npm init @amedia/kragl`",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-kragl": "bin/create-kragl.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"*"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test 'src/*-test.js'"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/amedia/eslint-config-amedia.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"kragl"
|
|
21
|
+
],
|
|
22
|
+
"author": "Amedia",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/amedia/eslint-config-amedia/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/amedia/eslint-config-amedia#readme",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
|
|
6
|
+
import { plan } from './plan.js';
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: npm init @amedia/kragl@latest -- [options]
|
|
9
|
+
|
|
10
|
+
Installs kragl in the project in the current directory, writes its config
|
|
11
|
+
files and adds the lint and format scripts. Running it again only adds what
|
|
12
|
+
is missing.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--biome use Biome instead of ESLint and Prettier
|
|
16
|
+
--no-lockfile-lint leave out the lockfile-lint runner
|
|
17
|
+
--ls-lint add the ls-lint runner
|
|
18
|
+
--browserslist add the Browserslist runner
|
|
19
|
+
-f, --force overwrite existing config files
|
|
20
|
+
-h, --help print this help text`;
|
|
21
|
+
|
|
22
|
+
// Lockfiles of the package managers kragl does not support.
|
|
23
|
+
const FOREIGN_LOCKFILES = [
|
|
24
|
+
'yarn.lock',
|
|
25
|
+
'pnpm-lock.yaml',
|
|
26
|
+
'bun.lock',
|
|
27
|
+
'bun.lockb',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const fail = (message) => {
|
|
31
|
+
console.error(`create-kragl: ${message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const npm = (args) =>
|
|
36
|
+
spawnSync('npm', args, {
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
shell: process.platform === 'win32',
|
|
39
|
+
}).status === 0;
|
|
40
|
+
|
|
41
|
+
const readPackageJson = () => JSON.parse(readFileSync('package.json', 'utf8'));
|
|
42
|
+
|
|
43
|
+
// Keeps the file's own indentation, the way npm does when it writes it.
|
|
44
|
+
const addScripts = (scripts) => {
|
|
45
|
+
const text = readFileSync('package.json', 'utf8');
|
|
46
|
+
const indent = text.match(/^[ \t]+/m)?.[0] ?? 2;
|
|
47
|
+
const packageJson = JSON.parse(text);
|
|
48
|
+
|
|
49
|
+
packageJson.scripts = { ...packageJson.scripts, ...scripts };
|
|
50
|
+
writeFileSync(
|
|
51
|
+
'package.json',
|
|
52
|
+
`${JSON.stringify(packageJson, null, indent)}\n`
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let values;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
({ values } = parseArgs({
|
|
60
|
+
options: {
|
|
61
|
+
biome: { type: 'boolean', default: false },
|
|
62
|
+
'no-lockfile-lint': { type: 'boolean', default: false },
|
|
63
|
+
'ls-lint': { type: 'boolean', default: false },
|
|
64
|
+
browserslist: { type: 'boolean', default: false },
|
|
65
|
+
force: { type: 'boolean', short: 'f', default: false },
|
|
66
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
fail(`${error.message}\n\n${HELP}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (values.help) {
|
|
74
|
+
console.log(HELP);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const foreign = FOREIGN_LOCKFILES.find((file) => existsSync(file));
|
|
79
|
+
|
|
80
|
+
if (foreign) {
|
|
81
|
+
fail(`found ${foreign}; kragl supports npm projects only.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The config files kragl writes are ES modules, and npm 11 makes new packages
|
|
85
|
+
// CommonJS unless told otherwise.
|
|
86
|
+
if (
|
|
87
|
+
!existsSync('package.json') &&
|
|
88
|
+
!npm(['init', '--yes', '--init-type=module'])
|
|
89
|
+
) {
|
|
90
|
+
fail('npm init failed.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { packages, configs, scripts } = plan(readPackageJson(), {
|
|
94
|
+
biome: values.biome,
|
|
95
|
+
'lockfile-lint': !values['no-lockfile-lint'],
|
|
96
|
+
'ls-lint': values['ls-lint'],
|
|
97
|
+
browserslist: values.browserslist,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
packages.length > 0 &&
|
|
102
|
+
!npm(['install', '--save-dev', '--save-exact', ...packages])
|
|
103
|
+
) {
|
|
104
|
+
fail('npm install failed.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Existing config files are kept unless --force. `--no` makes npm use the
|
|
108
|
+
// kragl just installed and never fetch one from the registry.
|
|
109
|
+
const kept = values.force
|
|
110
|
+
? []
|
|
111
|
+
: configs.filter(({ filename }) => existsSync(filename));
|
|
112
|
+
const failed = configs
|
|
113
|
+
.filter((config) => !kept.includes(config))
|
|
114
|
+
.filter(
|
|
115
|
+
({ tool }) =>
|
|
116
|
+
!npm([
|
|
117
|
+
'exec',
|
|
118
|
+
'--no',
|
|
119
|
+
'--',
|
|
120
|
+
'kragl',
|
|
121
|
+
'install',
|
|
122
|
+
`--${tool}`,
|
|
123
|
+
...(values.force ? ['--force'] : []),
|
|
124
|
+
])
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const scriptNames = Object.keys(scripts);
|
|
128
|
+
|
|
129
|
+
if (scriptNames.length > 0) {
|
|
130
|
+
addScripts(scripts);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(
|
|
135
|
+
packages.length > 0
|
|
136
|
+
? `Installed ${packages.join(', ')}.`
|
|
137
|
+
: 'All kragl packages were already installed.'
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (scriptNames.length > 0) {
|
|
141
|
+
console.log(`Added scripts: ${scriptNames.join(', ')}.`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (kept.length > 0) {
|
|
145
|
+
const filenames = kept.map(({ filename }) => filename).join(', ');
|
|
146
|
+
console.log(`Kept ${filenames}; re-run with --force to replace.`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (failed.length > 0) {
|
|
150
|
+
const tools = failed.map(({ tool }) => tool).join(', ');
|
|
151
|
+
fail(`kragl install failed for ${tools}, see above.`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log('Run `npm run lint` to lint the project.');
|
package/src/plan-test.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { plan, SCRIPTS } from './plan.js';
|
|
5
|
+
|
|
6
|
+
const defaults = { 'lockfile-lint': true };
|
|
7
|
+
|
|
8
|
+
describe('plan', () => {
|
|
9
|
+
test('sets up ESLint, Prettier and lockfile-lint in an empty project', () => {
|
|
10
|
+
assert.deepEqual(plan({}, defaults), {
|
|
11
|
+
packages: ['@amedia/kragl', '@amedia/kragl-lockfile-lint'],
|
|
12
|
+
configs: [
|
|
13
|
+
{ tool: 'eslint', filename: 'eslint.config.js' },
|
|
14
|
+
{ tool: 'prettier', filename: 'prettier.config.js' },
|
|
15
|
+
],
|
|
16
|
+
scripts: SCRIPTS,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('sets up Biome without ESLint and Prettier', () => {
|
|
21
|
+
const { packages, configs } = plan({}, { ...defaults, biome: true });
|
|
22
|
+
|
|
23
|
+
assert.deepEqual(packages, [
|
|
24
|
+
'@amedia/kragl-cli',
|
|
25
|
+
'@amedia/kragl-biome',
|
|
26
|
+
'@amedia/kragl-lockfile-lint',
|
|
27
|
+
]);
|
|
28
|
+
assert.deepEqual(configs, [{ tool: 'biome', filename: 'biome.json' }]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('stays on Biome when kragl-biome is installed', () => {
|
|
32
|
+
const packageJson = {
|
|
33
|
+
devDependencies: {
|
|
34
|
+
'@amedia/kragl-cli': '1.0.1',
|
|
35
|
+
'@amedia/kragl-biome': '1.3.1',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(plan(packageJson, defaults), {
|
|
40
|
+
packages: ['@amedia/kragl-lockfile-lint'],
|
|
41
|
+
configs: [{ tool: 'biome', filename: 'biome.json' }],
|
|
42
|
+
scripts: SCRIPTS,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('adds the opt-in runners and leaves out lockfile-lint', () => {
|
|
47
|
+
const { packages } = plan(
|
|
48
|
+
{},
|
|
49
|
+
{ 'lockfile-lint': false, 'ls-lint': true, browserslist: true }
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
assert.deepEqual(packages, [
|
|
53
|
+
'@amedia/kragl',
|
|
54
|
+
'@amedia/kragl-ls-lint',
|
|
55
|
+
'@amedia/kragl-browserslist',
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('skips packages in dependencies or devDependencies', () => {
|
|
60
|
+
const packageJson = {
|
|
61
|
+
dependencies: { '@amedia/kragl': '6.4.0' },
|
|
62
|
+
devDependencies: { '@amedia/kragl-lockfile-lint': '1.0.0' },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
assert.deepEqual(plan(packageJson, defaults).packages, []);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('keeps existing scripts', () => {
|
|
69
|
+
const packageJson = { scripts: { lint: 'eslint .' } };
|
|
70
|
+
|
|
71
|
+
assert.deepEqual(plan(packageJson, defaults).scripts, {
|
|
72
|
+
'lint:format': 'kragl format',
|
|
73
|
+
'lint:fix': 'kragl lint --fix',
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
package/src/plan.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Runners a project can opt into on top of the base setup, by flag name.
|
|
2
|
+
export const RUNNERS = {
|
|
3
|
+
'lockfile-lint': '@amedia/kragl-lockfile-lint',
|
|
4
|
+
'ls-lint': '@amedia/kragl-ls-lint',
|
|
5
|
+
browserslist: '@amedia/kragl-browserslist',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// The base setups, and the config file `kragl install --<tool>` writes for
|
|
9
|
+
// each of their tools.
|
|
10
|
+
const BASES = {
|
|
11
|
+
kragl: {
|
|
12
|
+
packages: ['@amedia/kragl'],
|
|
13
|
+
configs: [
|
|
14
|
+
{ tool: 'eslint', filename: 'eslint.config.js' },
|
|
15
|
+
{ tool: 'prettier', filename: 'prettier.config.js' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
biome: {
|
|
19
|
+
packages: ['@amedia/kragl-cli', '@amedia/kragl-biome'],
|
|
20
|
+
configs: [{ tool: 'biome', filename: 'biome.json' }],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const SCRIPTS = {
|
|
25
|
+
lint: 'kragl lint',
|
|
26
|
+
'lint:fix': 'kragl lint --fix',
|
|
27
|
+
'lint:format': 'kragl format',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Works out what `create-kragl` has to do in a project, leaving out what is
|
|
32
|
+
* already there, so running it again only fills in what is missing.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} packageJson - The project's package.json.
|
|
35
|
+
* @param {Object} options - The parsed flags: `biome` and one per runner.
|
|
36
|
+
* @returns {{ packages: string[], configs: Object[], scripts: Object }}
|
|
37
|
+
*/
|
|
38
|
+
export function plan(packageJson, options) {
|
|
39
|
+
const installed = new Set(
|
|
40
|
+
Object.keys({
|
|
41
|
+
...packageJson.dependencies,
|
|
42
|
+
...packageJson.devDependencies,
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// A project already on Biome stays on Biome without passing --biome again.
|
|
47
|
+
const base =
|
|
48
|
+
options.biome || installed.has('@amedia/kragl-biome')
|
|
49
|
+
? BASES.biome
|
|
50
|
+
: BASES.kragl;
|
|
51
|
+
|
|
52
|
+
const runners = Object.keys(RUNNERS)
|
|
53
|
+
.filter((name) => options[name])
|
|
54
|
+
.map((name) => RUNNERS[name]);
|
|
55
|
+
|
|
56
|
+
const packages = [...base.packages, ...runners].filter(
|
|
57
|
+
(name) => !installed.has(name)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const scripts = Object.fromEntries(
|
|
61
|
+
Object.entries(SCRIPTS).filter(([name]) => !packageJson.scripts?.[name])
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return { packages, configs: base.configs, scripts };
|
|
65
|
+
}
|