@plumeria/init 19.1.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/LICENSE +21 -0
- package/README.md +109 -0
- package/bin/init.js +13 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +248 -0
- package/dist/detect.d.ts +28 -0
- package/dist/detect.js +199 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -0
- package/dist/prompt.d.ts +7 -0
- package/dist/prompt.js +84 -0
- package/dist/setup.d.ts +58 -0
- package/dist/setup.js +495 -0
- package/dist/source.d.ts +30 -0
- package/dist/source.js +413 -0
- package/dist/style.d.ts +16 -0
- package/dist/style.js +21 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) zss-in-js contributer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @plumeria/init
|
|
2
|
+
|
|
3
|
+
Sets Plumeria up in a project that already exists. No install needed — run it
|
|
4
|
+
with `npx`.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npx @plumeria/init
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
It reads the project, asks what it cannot read, shows the plan, and only then
|
|
11
|
+
writes. Nothing is installed or patched before you say so.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
✔ detected next pnpm typescript
|
|
15
|
+
|
|
16
|
+
➡︎ install @plumeria/core @plumeria/next-plugin @plumeria/eslint-plugin oxlint
|
|
17
|
+
+ write plumeria.d.ts classStyle
|
|
18
|
+
~ patch next.config.ts withPlumeria(...)
|
|
19
|
+
~ patch eslint.config.ts plumeria.configs.recommended
|
|
20
|
+
~ patch package.json build: plumerialint -- next build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What it detects
|
|
24
|
+
|
|
25
|
+
The package manager comes from `packageManager` or the lockfile, both looked for
|
|
26
|
+
upwards from the directory init runs in, so a package inside a workspace is
|
|
27
|
+
installed with the manager the workspace root uses. The bundler comes from the
|
|
28
|
+
dependencies, and from the config files when nothing is installed yet: `next`, `vite`, `astro`, `webpack`, `rspack`, `rollup`,
|
|
29
|
+
`rolldown`, `esbuild`, `farm` and `bun`. Pass `--bundler <name>` to decide it
|
|
30
|
+
yourself.
|
|
31
|
+
|
|
32
|
+
Next.js gets `@plumeria/next-plugin`; every other bundler gets
|
|
33
|
+
`@plumeria/unplugin` with the factory that matches it.
|
|
34
|
+
|
|
35
|
+
## What it asks
|
|
36
|
+
|
|
37
|
+
**Which spelling of a two-named property does this project write?** —
|
|
38
|
+
`marginBlockStart` or `marginTop`. The answer reaches both sides at once: the
|
|
39
|
+
bundler plugin gets `withoutLogicalProperties` or `withoutPhysicalProperties`,
|
|
40
|
+
so the counterpart cannot compile, and ESLint gets
|
|
41
|
+
`@plumeria/no-logical-properties` or `@plumeria/no-physical-properties`, so it
|
|
42
|
+
is reported while you type. Answering "both" sets neither. `--sizes` extends
|
|
43
|
+
the answer to the size axis, where `width` and `inlineSize` are the pair.
|
|
44
|
+
|
|
45
|
+
**Expand a border shorthand into the three declarations it sets?** — turns on
|
|
46
|
+
`@plumeria/expand-border-shorthands`. A `border` shorthand crosses the axis
|
|
47
|
+
shorthands without either containing the other, so leaving it whole makes the
|
|
48
|
+
outcome depend on order.
|
|
49
|
+
|
|
50
|
+
**Which JSX prop carries styles?** — `classStyle` by default. A renamed prop is
|
|
51
|
+
written into the plugin as `styleProp` and declared in `plumeria.d.ts`, so the
|
|
52
|
+
two cannot disagree. It has to be an identifier TypeScript can declare, and a
|
|
53
|
+
name React leaves free; `className`, `style`, `key`, `ref` and `children` are
|
|
54
|
+
refused, from the prompt and from `--style-prop` alike.
|
|
55
|
+
|
|
56
|
+
**Set up `@plumeria/eslint-plugin` and the `plumerialint` build guard?** — adds
|
|
57
|
+
the plugin and `oxlint`, writes or extends the flat config, and prefixes the
|
|
58
|
+
`build` script with `plumerialint --`, so a style error stops the build.
|
|
59
|
+
|
|
60
|
+
## What it writes
|
|
61
|
+
|
|
62
|
+
- `plumeria.d.ts` — the styling prop TypeScript reads.
|
|
63
|
+
- the bundler config — the plugin, and the options the answers chose. An
|
|
64
|
+
existing config is patched: the import lands after the last import, and the
|
|
65
|
+
plugin joins the `plugins` array. A Next config has its default export
|
|
66
|
+
wrapped in `withPlumeria`.
|
|
67
|
+
- `eslint.config.ts` — `plumeria.configs.recommended` plus the rules that were
|
|
68
|
+
asked for. An existing flat config is extended rather than replaced.
|
|
69
|
+
- `package.json` — `plumerialint --` in front of the `build` script, and on
|
|
70
|
+
Next.js `rimraf .next` before `dev` and before `build`, so a version change is
|
|
71
|
+
not read as a compile error. A `pre` script is added only for a script the
|
|
72
|
+
project actually has, and one it already wrote is left alone. Clearing the
|
|
73
|
+
cache is a Next.js concern rather than a lint one, so `--no-eslint` keeps it.
|
|
74
|
+
|
|
75
|
+
What counts as already set up is the plugin being *called*, not merely imported.
|
|
76
|
+
A bare `plumeria.version`, and a call inside a comment, a string or a regex
|
|
77
|
+
literal, all read as not set up, because none of them registers anything; an import nothing
|
|
78
|
+
calls is completed rather than skipped, and the binding the file already named is
|
|
79
|
+
the one that gets called. An import spanning several lines is read as one
|
|
80
|
+
statement, so it is never duplicated. A second run writes nothing; a
|
|
81
|
+
half-finished one is finished.
|
|
82
|
+
|
|
83
|
+
The `plugins` array it extends is the one the config object owns — a `plugins`
|
|
84
|
+
belonging to a nested key, such as `test` for Vitest, is never written into.
|
|
85
|
+
Where the plugin cannot be placed safely — a config whose own object has no
|
|
86
|
+
`plugins` array, a build script rather than a config, a legacy `.eslintrc` —
|
|
87
|
+
nothing is touched and the snippet is printed instead.
|
|
88
|
+
|
|
89
|
+
## Options
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
npx @plumeria/init --dry-run # show the plan, write nothing
|
|
93
|
+
npx @plumeria/init --yes # take every default, ask nothing
|
|
94
|
+
npx @plumeria/init --physical --sizes # answer the spelling question up front
|
|
95
|
+
npx @plumeria/init --style-prop sx
|
|
96
|
+
npx @plumeria/init --bundler rollup
|
|
97
|
+
npx @plumeria/init --no-eslint # leave ESLint and the plumerialint guard out
|
|
98
|
+
npx @plumeria/init --no-install # write the configs, print the command
|
|
99
|
+
npx @plumeria/init --cwd packages/app
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Without a TTY it never asks — every unanswered question takes its default,
|
|
103
|
+
which is what makes it usable in CI. Colour comes from `node:util`'s
|
|
104
|
+
`styleText`, which leaves the text alone when the output is not a terminal, so a
|
|
105
|
+
piped or logged run carries no escape codes.
|
|
106
|
+
|
|
107
|
+
Installed rather than fetched, the command is `plumeria-init`.
|
|
108
|
+
|
|
109
|
+
Commit before running, so the writes can be reverted with `git checkout`.
|
package/bin/init.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { main } = require('../dist/cli.js');
|
|
4
|
+
|
|
5
|
+
main(process.argv.slice(2)).then(
|
|
6
|
+
(code) => {
|
|
7
|
+
process.exitCode = code;
|
|
8
|
+
},
|
|
9
|
+
(error) => {
|
|
10
|
+
console.error(`✖ ${(error && error.message) || error}`);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
},
|
|
13
|
+
);
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Bundler } from './detect';
|
|
2
|
+
import type { Answers } from './setup';
|
|
3
|
+
interface Options {
|
|
4
|
+
cwd: string;
|
|
5
|
+
bundler: Bundler | undefined;
|
|
6
|
+
preset: Partial<Answers>;
|
|
7
|
+
yes: boolean;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseArgs(argv: string[]): Options | null;
|
|
11
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
12
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseArgs = parseArgs;
|
|
7
|
+
exports.main = main;
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const detect_1 = require("./detect");
|
|
12
|
+
const prompt_1 = require("./prompt");
|
|
13
|
+
const setup_1 = require("./setup");
|
|
14
|
+
const style_1 = require("./style");
|
|
15
|
+
const { version } = require('../package.json');
|
|
16
|
+
const USAGE = `@plumeria/init — set up Plumeria in this project
|
|
17
|
+
|
|
18
|
+
Usage
|
|
19
|
+
npx @plumeria/init
|
|
20
|
+
|
|
21
|
+
It reads the project, asks what it cannot detect, then installs the packages
|
|
22
|
+
and writes the configs. Nothing is written before the plan is shown.
|
|
23
|
+
|
|
24
|
+
Options
|
|
25
|
+
-y, --yes accept every default, ask nothing
|
|
26
|
+
-d, --dry-run show the plan without writing
|
|
27
|
+
--bundler <name> ${detect_1.BUNDLERS.join(', ')}
|
|
28
|
+
--style-prop <p> the JSX prop that carries styles (default classStyle)
|
|
29
|
+
--logical this project writes logical properties
|
|
30
|
+
--physical this project writes physical properties
|
|
31
|
+
--sizes extend the spelling policy to the size axis
|
|
32
|
+
--no-eslint leave ESLint and the plumerialint guard out
|
|
33
|
+
--no-install write the configs, print the install command
|
|
34
|
+
--cwd <dir> run against another directory
|
|
35
|
+
-h, --help show this message
|
|
36
|
+
-v, --version show the version
|
|
37
|
+
|
|
38
|
+
The spelling answer reaches both sides: the bundler plugin rejects the
|
|
39
|
+
counterpart at compile time, and ESLint reports it while you type.`;
|
|
40
|
+
class UsageError extends Error {
|
|
41
|
+
}
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
44
|
+
console.log(USAGE);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (argv.includes('-v') || argv.includes('--version')) {
|
|
48
|
+
console.log(version);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const options = {
|
|
52
|
+
cwd: process.cwd(),
|
|
53
|
+
bundler: undefined,
|
|
54
|
+
preset: {},
|
|
55
|
+
yes: false,
|
|
56
|
+
dryRun: false,
|
|
57
|
+
};
|
|
58
|
+
for (let i = 0; i < argv.length; i++) {
|
|
59
|
+
const arg = argv[i];
|
|
60
|
+
switch (arg) {
|
|
61
|
+
case '-y':
|
|
62
|
+
case '--yes':
|
|
63
|
+
options.yes = true;
|
|
64
|
+
break;
|
|
65
|
+
case '-d':
|
|
66
|
+
case '--dry-run':
|
|
67
|
+
options.dryRun = true;
|
|
68
|
+
break;
|
|
69
|
+
case '--logical':
|
|
70
|
+
options.preset.spelling = 'logical';
|
|
71
|
+
break;
|
|
72
|
+
case '--physical':
|
|
73
|
+
options.preset.spelling = 'physical';
|
|
74
|
+
break;
|
|
75
|
+
case '--both':
|
|
76
|
+
options.preset.spelling = 'both';
|
|
77
|
+
break;
|
|
78
|
+
case '--sizes':
|
|
79
|
+
options.preset.sizes = true;
|
|
80
|
+
break;
|
|
81
|
+
case '--no-eslint':
|
|
82
|
+
options.preset.eslint = false;
|
|
83
|
+
break;
|
|
84
|
+
case '--no-install':
|
|
85
|
+
options.preset.install = false;
|
|
86
|
+
break;
|
|
87
|
+
case '--bundler':
|
|
88
|
+
options.bundler = read(argv, ++i, '--bundler');
|
|
89
|
+
break;
|
|
90
|
+
case '--style-prop':
|
|
91
|
+
options.preset.styleProp = styleProp(read(argv, ++i, '--style-prop'));
|
|
92
|
+
break;
|
|
93
|
+
case '--cwd':
|
|
94
|
+
options.cwd = node_path_1.default.resolve(read(argv, ++i, '--cwd'));
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
throw new UsageError(`unknown option "${arg}"`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (options.bundler && !detect_1.BUNDLERS.includes(options.bundler)) {
|
|
101
|
+
throw new UsageError(`unknown bundler "${options.bundler}". Available: ${detect_1.BUNDLERS.join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
if (options.preset.sizes && options.preset.spelling === undefined) {
|
|
104
|
+
throw new UsageError('--sizes needs --logical or --physical');
|
|
105
|
+
}
|
|
106
|
+
return options;
|
|
107
|
+
}
|
|
108
|
+
function styleProp(value) {
|
|
109
|
+
try {
|
|
110
|
+
return (0, setup_1.assertStyleProp)(value);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw new UsageError(error.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function read(argv, index, flag) {
|
|
117
|
+
const value = argv[index];
|
|
118
|
+
if (!value || value.startsWith('-'))
|
|
119
|
+
throw new UsageError(`${flag} needs a value`);
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
const MARKS = {
|
|
123
|
+
install: '➡︎',
|
|
124
|
+
write: '+',
|
|
125
|
+
patch: '~',
|
|
126
|
+
manual: '!',
|
|
127
|
+
skip: '·',
|
|
128
|
+
};
|
|
129
|
+
const PAINT = {
|
|
130
|
+
install: style_1.style.install,
|
|
131
|
+
write: style_1.style.write,
|
|
132
|
+
patch: style_1.style.patch,
|
|
133
|
+
manual: style_1.style.manual,
|
|
134
|
+
skip: style_1.style.skip,
|
|
135
|
+
};
|
|
136
|
+
function show(actions) {
|
|
137
|
+
const label = Math.max(...actions.map((action) => action.label.length));
|
|
138
|
+
const named = actions.filter((action) => action.kind !== 'install');
|
|
139
|
+
const file = Math.max(0, ...named.map((action) => action.file.length));
|
|
140
|
+
const pad = (text, width) => ' '.repeat(Math.max(0, width - text.length));
|
|
141
|
+
for (const action of actions) {
|
|
142
|
+
const head = PAINT[action.kind](`${MARKS[action.kind]} ${action.label}`) +
|
|
143
|
+
pad(action.label, label);
|
|
144
|
+
const target = action.kind === 'install'
|
|
145
|
+
? ''
|
|
146
|
+
: ` ${style_1.style.strong(action.file)}${pad(action.file, file)}`;
|
|
147
|
+
console.log(` ${head}${target} ${action.note}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function apply(root, action) {
|
|
151
|
+
if (action.kind !== 'write' && action.kind !== 'patch')
|
|
152
|
+
return;
|
|
153
|
+
const file = node_path_1.default.join(root, action.file);
|
|
154
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
|
|
155
|
+
node_fs_1.default.writeFileSync(file, action.contents);
|
|
156
|
+
}
|
|
157
|
+
function install(root, action) {
|
|
158
|
+
if (action.kind !== 'install')
|
|
159
|
+
return 0;
|
|
160
|
+
const [command, ...args] = action.command.split(' ');
|
|
161
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
162
|
+
cwd: root,
|
|
163
|
+
stdio: 'inherit',
|
|
164
|
+
shell: process.platform === 'win32',
|
|
165
|
+
});
|
|
166
|
+
return result.status ?? 1;
|
|
167
|
+
}
|
|
168
|
+
async function main(argv) {
|
|
169
|
+
let options;
|
|
170
|
+
try {
|
|
171
|
+
options = parseArgs(argv);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (!(error instanceof UsageError))
|
|
175
|
+
throw error;
|
|
176
|
+
console.error(`${style_1.style.failure('✖')} ${error.message}\n`);
|
|
177
|
+
console.error(USAGE);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
if (!options)
|
|
181
|
+
return 0;
|
|
182
|
+
const detected = (0, detect_1.detect)(options.cwd, options.bundler);
|
|
183
|
+
const found = [
|
|
184
|
+
detected.bundler,
|
|
185
|
+
detected.packageManager,
|
|
186
|
+
detected.typescript ? 'typescript' : 'javascript',
|
|
187
|
+
]
|
|
188
|
+
.map((name) => style_1.style.strong(name))
|
|
189
|
+
.join(' ');
|
|
190
|
+
console.log(`\n${style_1.style.ok('✔')} detected ${found}`);
|
|
191
|
+
const interactive = !options.yes && process.stdin.isTTY === true;
|
|
192
|
+
let asker;
|
|
193
|
+
let answers;
|
|
194
|
+
try {
|
|
195
|
+
if (interactive) {
|
|
196
|
+
asker = (0, prompt_1.terminal)();
|
|
197
|
+
answers = await (0, prompt_1.ask)(asker, options.preset);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
answers = { ...setup_1.DEFAULT_ANSWERS, ...options.preset };
|
|
201
|
+
}
|
|
202
|
+
const actions = (0, setup_1.plan)(detected, answers);
|
|
203
|
+
console.log('');
|
|
204
|
+
show(actions);
|
|
205
|
+
if (options.dryRun) {
|
|
206
|
+
console.log(`\n${style_1.style.faint('Run without --dry-run to apply.')}`);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
if (interactive && asker) {
|
|
210
|
+
const go = (await asker.question('\nApply? (Y/n) ')).trim().toLowerCase();
|
|
211
|
+
if (go !== '' && go !== 'y' && go !== 'yes') {
|
|
212
|
+
console.log(style_1.style.faint('Nothing was written.'));
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const action of actions)
|
|
217
|
+
apply(detected.root, action);
|
|
218
|
+
const wanted = actions.find((action) => action.kind === 'install');
|
|
219
|
+
if (wanted && answers.install) {
|
|
220
|
+
console.log('');
|
|
221
|
+
const status = install(detected.root, wanted);
|
|
222
|
+
if (status !== 0) {
|
|
223
|
+
console.error(`\n${style_1.style.failure('✖')} ${wanted.command} failed. Run it yourself and try again.`);
|
|
224
|
+
return status;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (wanted) {
|
|
228
|
+
console.log(`\n${style_1.style.install('➡︎')} run: ${wanted.command}`);
|
|
229
|
+
}
|
|
230
|
+
console.log(`\n${style_1.style.ok('✔')} Plumeria is set up.`);
|
|
231
|
+
const manual = actions.filter((action) => action.kind === 'manual');
|
|
232
|
+
if (manual.length === 0)
|
|
233
|
+
return 0;
|
|
234
|
+
console.log(`\n${style_1.style.manual('Left to you — these could not be written safely:')}\n`);
|
|
235
|
+
for (const action of manual) {
|
|
236
|
+
if (action.kind !== 'manual')
|
|
237
|
+
continue;
|
|
238
|
+
console.log(` ${style_1.style.strong(action.file)} ${action.note}`);
|
|
239
|
+
for (const line of action.snippet.split('\n'))
|
|
240
|
+
console.log(style_1.style.faint(` ${line}`));
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
asker?.close();
|
|
247
|
+
}
|
|
248
|
+
}
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
2
|
+
export type Bundler = 'next' | 'vite' | 'astro' | 'webpack' | 'rspack' | 'rollup' | 'rolldown' | 'esbuild' | 'farm' | 'bun';
|
|
3
|
+
export interface Manifest {
|
|
4
|
+
type?: string;
|
|
5
|
+
packageManager?: string;
|
|
6
|
+
scripts?: Record<string, string>;
|
|
7
|
+
dependencies?: Record<string, string>;
|
|
8
|
+
devDependencies?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export interface Detected {
|
|
11
|
+
root: string;
|
|
12
|
+
manifest: Manifest;
|
|
13
|
+
packageManager: PackageManager;
|
|
14
|
+
bundler: Bundler;
|
|
15
|
+
bundlerConfig: string | undefined;
|
|
16
|
+
eslintConfig: string | undefined;
|
|
17
|
+
declaration: string | undefined;
|
|
18
|
+
typescript: boolean;
|
|
19
|
+
esm: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const BUNDLERS: readonly Bundler[];
|
|
22
|
+
export declare const readManifest: (root: string) => Manifest;
|
|
23
|
+
export declare const installed: (manifest: Manifest) => Record<string, string>;
|
|
24
|
+
export declare const detectPackageManager: (root: string, manifest: Manifest) => PackageManager;
|
|
25
|
+
export declare const detectBundler: (root: string, manifest: Manifest) => Bundler | undefined;
|
|
26
|
+
export declare const detect: (root: string, override?: Bundler) => Detected;
|
|
27
|
+
export declare const defaultConfigName: (bundler: Bundler, typescript: boolean) => string;
|
|
28
|
+
export declare const configNames: (bundler: Bundler) => readonly string[];
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configNames = exports.defaultConfigName = exports.detect = exports.detectBundler = exports.detectPackageManager = exports.installed = exports.readManifest = exports.BUNDLERS = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
exports.BUNDLERS = [
|
|
10
|
+
'next',
|
|
11
|
+
'vite',
|
|
12
|
+
'astro',
|
|
13
|
+
'webpack',
|
|
14
|
+
'rspack',
|
|
15
|
+
'rollup',
|
|
16
|
+
'rolldown',
|
|
17
|
+
'esbuild',
|
|
18
|
+
'farm',
|
|
19
|
+
'bun',
|
|
20
|
+
];
|
|
21
|
+
const LOCKFILES = [
|
|
22
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
23
|
+
['bun.lock', 'bun'],
|
|
24
|
+
['bun.lockb', 'bun'],
|
|
25
|
+
['yarn.lock', 'yarn'],
|
|
26
|
+
['package-lock.json', 'npm'],
|
|
27
|
+
];
|
|
28
|
+
const CONFIG_NAMES = {
|
|
29
|
+
next: [
|
|
30
|
+
'next.config.ts',
|
|
31
|
+
'next.config.mts',
|
|
32
|
+
'next.config.mjs',
|
|
33
|
+
'next.config.js',
|
|
34
|
+
'next.config.cjs',
|
|
35
|
+
],
|
|
36
|
+
vite: [
|
|
37
|
+
'vite.config.ts',
|
|
38
|
+
'vite.config.mts',
|
|
39
|
+
'vite.config.mjs',
|
|
40
|
+
'vite.config.js',
|
|
41
|
+
],
|
|
42
|
+
astro: [
|
|
43
|
+
'astro.config.ts',
|
|
44
|
+
'astro.config.mts',
|
|
45
|
+
'astro.config.mjs',
|
|
46
|
+
'astro.config.js',
|
|
47
|
+
],
|
|
48
|
+
webpack: [
|
|
49
|
+
'webpack.config.ts',
|
|
50
|
+
'webpack.config.mjs',
|
|
51
|
+
'webpack.config.js',
|
|
52
|
+
'webpack.config.cjs',
|
|
53
|
+
],
|
|
54
|
+
rspack: [
|
|
55
|
+
'rspack.config.ts',
|
|
56
|
+
'rspack.config.mjs',
|
|
57
|
+
'rspack.config.js',
|
|
58
|
+
'rspack.config.cjs',
|
|
59
|
+
],
|
|
60
|
+
rollup: ['rollup.config.ts', 'rollup.config.mjs', 'rollup.config.js'],
|
|
61
|
+
rolldown: ['rolldown.config.ts', 'rolldown.config.mjs', 'rolldown.config.js'],
|
|
62
|
+
esbuild: ['build.ts', 'build.mjs', 'build.js', 'esbuild.config.js'],
|
|
63
|
+
farm: ['farm.config.ts', 'farm.config.mjs', 'farm.config.js'],
|
|
64
|
+
bun: ['build.ts', 'build.mjs', 'build.js'],
|
|
65
|
+
};
|
|
66
|
+
const ESLINT_NAMES = [
|
|
67
|
+
'eslint.config.ts',
|
|
68
|
+
'eslint.config.mts',
|
|
69
|
+
'eslint.config.mjs',
|
|
70
|
+
'eslint.config.js',
|
|
71
|
+
'eslint.config.cjs',
|
|
72
|
+
'.eslintrc.json',
|
|
73
|
+
'.eslintrc.js',
|
|
74
|
+
'.eslintrc.cjs',
|
|
75
|
+
'.eslintrc',
|
|
76
|
+
];
|
|
77
|
+
const DECLARATION_NAMES = [
|
|
78
|
+
'plumeria.d.ts',
|
|
79
|
+
'src/plumeria.d.ts',
|
|
80
|
+
'app/plumeria.d.ts',
|
|
81
|
+
'types/plumeria.d.ts',
|
|
82
|
+
];
|
|
83
|
+
const readManifest = (root) => {
|
|
84
|
+
const file = node_path_1.default.join(root, 'package.json');
|
|
85
|
+
if (!node_fs_1.default.existsSync(file)) {
|
|
86
|
+
throw new Error(`no package.json in ${root}. Run this from the project root.`);
|
|
87
|
+
}
|
|
88
|
+
return JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
89
|
+
};
|
|
90
|
+
exports.readManifest = readManifest;
|
|
91
|
+
const installed = (manifest) => ({
|
|
92
|
+
...manifest.dependencies,
|
|
93
|
+
...manifest.devDependencies,
|
|
94
|
+
});
|
|
95
|
+
exports.installed = installed;
|
|
96
|
+
const named = (value) => value === 'pnpm' || value === 'yarn' || value === 'bun' || value === 'npm'
|
|
97
|
+
? value
|
|
98
|
+
: undefined;
|
|
99
|
+
const declaredIn = (dir) => {
|
|
100
|
+
const file = node_path_1.default.join(dir, 'package.json');
|
|
101
|
+
if (!node_fs_1.default.existsSync(file))
|
|
102
|
+
return undefined;
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
105
|
+
return named(parsed.packageManager?.split('@')[0]);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const detectPackageManager = (root, manifest) => {
|
|
112
|
+
const own = named(manifest.packageManager?.split('@')[0]);
|
|
113
|
+
if (own)
|
|
114
|
+
return own;
|
|
115
|
+
for (let dir = root;;) {
|
|
116
|
+
for (const [lockfile, manager] of LOCKFILES) {
|
|
117
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(dir, lockfile)))
|
|
118
|
+
return manager;
|
|
119
|
+
}
|
|
120
|
+
if (dir !== root) {
|
|
121
|
+
const declared = declaredIn(dir);
|
|
122
|
+
if (declared)
|
|
123
|
+
return declared;
|
|
124
|
+
}
|
|
125
|
+
const parent = node_path_1.default.dirname(dir);
|
|
126
|
+
if (parent === dir)
|
|
127
|
+
break;
|
|
128
|
+
dir = parent;
|
|
129
|
+
}
|
|
130
|
+
return 'npm';
|
|
131
|
+
};
|
|
132
|
+
exports.detectPackageManager = detectPackageManager;
|
|
133
|
+
const firstExisting = (root, names) => names.find((name) => node_fs_1.default.existsSync(node_path_1.default.join(root, name)));
|
|
134
|
+
const detectBundler = (root, manifest) => {
|
|
135
|
+
const deps = (0, exports.installed)(manifest);
|
|
136
|
+
const has = (name) => name in deps;
|
|
137
|
+
if (has('next'))
|
|
138
|
+
return 'next';
|
|
139
|
+
if (has('astro'))
|
|
140
|
+
return 'astro';
|
|
141
|
+
if (has('@rspack/core') || has('@rspack/cli'))
|
|
142
|
+
return 'rspack';
|
|
143
|
+
if (has('@farmfe/core'))
|
|
144
|
+
return 'farm';
|
|
145
|
+
if (has('vite') || has('@react-router/dev'))
|
|
146
|
+
return 'vite';
|
|
147
|
+
if (has('rolldown'))
|
|
148
|
+
return 'rolldown';
|
|
149
|
+
if (has('rollup'))
|
|
150
|
+
return 'rollup';
|
|
151
|
+
if (has('webpack'))
|
|
152
|
+
return 'webpack';
|
|
153
|
+
if (has('esbuild'))
|
|
154
|
+
return 'esbuild';
|
|
155
|
+
for (const bundler of exports.BUNDLERS) {
|
|
156
|
+
if (bundler === 'bun')
|
|
157
|
+
continue;
|
|
158
|
+
if (firstExisting(root, CONFIG_NAMES[bundler]))
|
|
159
|
+
return bundler;
|
|
160
|
+
}
|
|
161
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(root, 'bun.lock')) ||
|
|
162
|
+
node_fs_1.default.existsSync(node_path_1.default.join(root, 'bun.lockb'))) {
|
|
163
|
+
return 'bun';
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
};
|
|
167
|
+
exports.detectBundler = detectBundler;
|
|
168
|
+
const detect = (root, override) => {
|
|
169
|
+
const manifest = (0, exports.readManifest)(root);
|
|
170
|
+
const bundler = override ?? (0, exports.detectBundler)(root, manifest);
|
|
171
|
+
if (!bundler) {
|
|
172
|
+
throw new Error(`no bundler found. Pass --bundler <${exports.BUNDLERS.join('|')}>.`);
|
|
173
|
+
}
|
|
174
|
+
const declaration = firstExisting(root, DECLARATION_NAMES);
|
|
175
|
+
const typescript = node_fs_1.default.existsSync(node_path_1.default.join(root, 'tsconfig.json')) ||
|
|
176
|
+
'typescript' in (0, exports.installed)(manifest);
|
|
177
|
+
return {
|
|
178
|
+
root,
|
|
179
|
+
manifest,
|
|
180
|
+
packageManager: (0, exports.detectPackageManager)(root, manifest),
|
|
181
|
+
bundler,
|
|
182
|
+
bundlerConfig: firstExisting(root, CONFIG_NAMES[bundler]),
|
|
183
|
+
eslintConfig: firstExisting(root, ESLINT_NAMES),
|
|
184
|
+
declaration,
|
|
185
|
+
typescript,
|
|
186
|
+
esm: manifest.type === 'module',
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
exports.detect = detect;
|
|
190
|
+
const defaultConfigName = (bundler, typescript) => {
|
|
191
|
+
const names = CONFIG_NAMES[bundler];
|
|
192
|
+
const ending = (suffix) => names.find((name) => name.endsWith(suffix));
|
|
193
|
+
if (typescript)
|
|
194
|
+
return ending('.ts') ?? names[0];
|
|
195
|
+
return ending('.mjs') ?? ending('.js') ?? names[0];
|
|
196
|
+
};
|
|
197
|
+
exports.defaultConfigName = defaultConfigName;
|
|
198
|
+
const configNames = (bundler) => CONFIG_NAMES[bundler];
|
|
199
|
+
exports.configNames = configNames;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.main = exports.plan = exports.detectPackageManager = exports.detectBundler = exports.detect = void 0;
|
|
4
|
+
var detect_1 = require("./detect");
|
|
5
|
+
Object.defineProperty(exports, "detect", { enumerable: true, get: function () { return detect_1.detect; } });
|
|
6
|
+
Object.defineProperty(exports, "detectBundler", { enumerable: true, get: function () { return detect_1.detectBundler; } });
|
|
7
|
+
Object.defineProperty(exports, "detectPackageManager", { enumerable: true, get: function () { return detect_1.detectPackageManager; } });
|
|
8
|
+
var setup_1 = require("./setup");
|
|
9
|
+
Object.defineProperty(exports, "plan", { enumerable: true, get: function () { return setup_1.plan; } });
|
|
10
|
+
var cli_1 = require("./cli");
|
|
11
|
+
Object.defineProperty(exports, "main", { enumerable: true, get: function () { return cli_1.main; } });
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Answers } from './setup';
|
|
2
|
+
export interface Asker {
|
|
3
|
+
question: (prompt: string) => Promise<string>;
|
|
4
|
+
close: () => void;
|
|
5
|
+
}
|
|
6
|
+
export declare const terminal: () => Asker;
|
|
7
|
+
export declare const ask: (asker: Asker, preset: Partial<Answers>) => Promise<Answers>;
|