@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/dist/prompt.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
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.ask = exports.terminal = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
8
|
+
const setup_1 = require("./setup");
|
|
9
|
+
const style_1 = require("./style");
|
|
10
|
+
const SPELLINGS = {
|
|
11
|
+
'1': 'logical',
|
|
12
|
+
'2': 'physical',
|
|
13
|
+
'3': 'both',
|
|
14
|
+
l: 'logical',
|
|
15
|
+
logical: 'logical',
|
|
16
|
+
p: 'physical',
|
|
17
|
+
physical: 'physical',
|
|
18
|
+
b: 'both',
|
|
19
|
+
both: 'both',
|
|
20
|
+
};
|
|
21
|
+
const terminal = () => {
|
|
22
|
+
const rl = promises_1.default.createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
question: (prompt) => rl.question(prompt),
|
|
28
|
+
close: () => rl.close(),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
exports.terminal = terminal;
|
|
32
|
+
const confirm = async (asker, prompt, fallback) => {
|
|
33
|
+
const answer = (await asker.question(`${prompt} ${style_1.style.faint(fallback ? '(Y/n)' : '(y/N)')} `))
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase();
|
|
36
|
+
if (answer === '')
|
|
37
|
+
return fallback;
|
|
38
|
+
return answer === 'y' || answer === 'yes';
|
|
39
|
+
};
|
|
40
|
+
const ask = async (asker, preset) => {
|
|
41
|
+
const answers = { ...setup_1.DEFAULT_ANSWERS, ...preset };
|
|
42
|
+
if (preset.spelling === undefined) {
|
|
43
|
+
const choices = [
|
|
44
|
+
['1', 'logical', 'marginBlockStart, insetInlineStart'],
|
|
45
|
+
['2', 'physical', 'marginTop, left'],
|
|
46
|
+
['3', 'both', 'no policy'],
|
|
47
|
+
];
|
|
48
|
+
console.log(`\nWhich spelling of a two-named property does this project write?`);
|
|
49
|
+
for (const [key, name, hint] of choices) {
|
|
50
|
+
console.log(` ${style_1.style.choice(`${key}:`)} ${name.padEnd(9)}${style_1.style.faint(hint)}`);
|
|
51
|
+
}
|
|
52
|
+
console.log('');
|
|
53
|
+
for (;;) {
|
|
54
|
+
const answer = (await asker.question(`Spelling ${style_1.style.faint('(1/2/3) [3]')} `))
|
|
55
|
+
.trim()
|
|
56
|
+
.toLowerCase();
|
|
57
|
+
if (answer === '')
|
|
58
|
+
break;
|
|
59
|
+
const spelling = SPELLINGS[answer];
|
|
60
|
+
if (spelling) {
|
|
61
|
+
answers.spelling = spelling;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
console.log(`${style_1.style.failure('✖')} answer 1, 2 or 3.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (answers.spelling !== 'both' && preset.sizes === undefined) {
|
|
68
|
+
answers.sizes = await confirm(asker, 'Reject the size axis too (width/height ↔ inlineSize/blockSize)?', false);
|
|
69
|
+
}
|
|
70
|
+
if (preset.expandBorderShorthands === undefined) {
|
|
71
|
+
answers.expandBorderShorthands = await confirm(asker, 'Expand a border shorthand into the three declarations it sets?', true);
|
|
72
|
+
}
|
|
73
|
+
if (preset.styleProp === undefined) {
|
|
74
|
+
const answer = (await asker.question(`Which JSX prop carries styles? ${style_1.style.faint(`[${setup_1.DEFAULT_STYLE_PROP}]`)} `)).trim();
|
|
75
|
+
if (answer !== '') {
|
|
76
|
+
answers.styleProp = (0, setup_1.assertStyleProp)(answer);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (preset.eslint === undefined) {
|
|
80
|
+
answers.eslint = await confirm(asker, 'Set up @plumeria/eslint-plugin and the plumerialint build guard?', true);
|
|
81
|
+
}
|
|
82
|
+
return answers;
|
|
83
|
+
};
|
|
84
|
+
exports.ask = ask;
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Bundler, Detected, PackageManager } from './detect';
|
|
2
|
+
export type Spelling = 'logical' | 'physical' | 'both';
|
|
3
|
+
export interface Answers {
|
|
4
|
+
spelling: Spelling;
|
|
5
|
+
sizes: boolean;
|
|
6
|
+
styleProp: string;
|
|
7
|
+
expandBorderShorthands: boolean;
|
|
8
|
+
eslint: boolean;
|
|
9
|
+
install: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type Action = {
|
|
12
|
+
kind: 'install';
|
|
13
|
+
label: string;
|
|
14
|
+
note: string;
|
|
15
|
+
command: string;
|
|
16
|
+
packages: string[];
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'write';
|
|
19
|
+
label: string;
|
|
20
|
+
note: string;
|
|
21
|
+
file: string;
|
|
22
|
+
contents: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'patch';
|
|
25
|
+
label: string;
|
|
26
|
+
note: string;
|
|
27
|
+
file: string;
|
|
28
|
+
contents: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'manual';
|
|
31
|
+
label: string;
|
|
32
|
+
note: string;
|
|
33
|
+
file: string;
|
|
34
|
+
snippet: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'skip';
|
|
37
|
+
label: string;
|
|
38
|
+
note: string;
|
|
39
|
+
file: string;
|
|
40
|
+
};
|
|
41
|
+
export declare const DEFAULT_STYLE_PROP = "classStyle";
|
|
42
|
+
export declare const assertStyleProp: (value: string) => string;
|
|
43
|
+
export declare const DEFAULT_ANSWERS: Answers;
|
|
44
|
+
export declare const rejectedSpelling: (spelling: Spelling) => "logical" | "physical" | undefined;
|
|
45
|
+
export declare const compilerOption: (answers: Answers) => string | undefined;
|
|
46
|
+
export declare const spellingRule: (answers: Answers) => string | undefined;
|
|
47
|
+
export declare const pluginOptions: (answers: Answers) => string;
|
|
48
|
+
export declare const pluginCall: (bundler: Bundler, answers: Answers, local?: string) => string;
|
|
49
|
+
export declare const declarationSource: (answers: Answers) => string;
|
|
50
|
+
export declare const bundlerSource: (bundler: Bundler, answers: Answers, typescript: boolean) => string | undefined;
|
|
51
|
+
export declare const eslintSource: (answers: Answers, typescript: boolean) => string;
|
|
52
|
+
export declare const eslintEntries: (answers: Answers, typescript: boolean) => string[];
|
|
53
|
+
export declare const packages: (detected: Detected, answers: Answers) => string[];
|
|
54
|
+
export declare const installCommand: (manager: PackageManager, names: readonly string[]) => string;
|
|
55
|
+
export declare const buildScript: (existing: string) => string;
|
|
56
|
+
export declare const plan: (detected: Detected, answers: Answers) => Action[];
|
|
57
|
+
export declare const CLEAN_SCRIPT = "rimraf .next";
|
|
58
|
+
export declare const cleanScripts: (scripts: Record<string, string>) => Record<string, string>;
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
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.cleanScripts = exports.CLEAN_SCRIPT = exports.plan = exports.buildScript = exports.installCommand = exports.packages = exports.eslintEntries = exports.eslintSource = exports.bundlerSource = exports.declarationSource = exports.pluginCall = exports.pluginOptions = exports.spellingRule = exports.compilerOption = exports.rejectedSpelling = exports.DEFAULT_ANSWERS = exports.assertStyleProp = exports.DEFAULT_STYLE_PROP = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const detect_1 = require("./detect");
|
|
10
|
+
const source_1 = require("./source");
|
|
11
|
+
exports.DEFAULT_STYLE_PROP = 'classStyle';
|
|
12
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
13
|
+
const RESERVED = new Set(['className', 'key', 'ref', 'children', 'style']);
|
|
14
|
+
const assertStyleProp = (value) => {
|
|
15
|
+
if (!IDENTIFIER.test(value))
|
|
16
|
+
throw new Error(`"${value}" is not a valid identifier`);
|
|
17
|
+
if (RESERVED.has(value))
|
|
18
|
+
throw new Error(`"${value}" is already used by React`);
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
exports.assertStyleProp = assertStyleProp;
|
|
22
|
+
exports.DEFAULT_ANSWERS = {
|
|
23
|
+
spelling: 'both',
|
|
24
|
+
sizes: false,
|
|
25
|
+
styleProp: exports.DEFAULT_STYLE_PROP,
|
|
26
|
+
expandBorderShorthands: true,
|
|
27
|
+
eslint: true,
|
|
28
|
+
install: true,
|
|
29
|
+
};
|
|
30
|
+
const UNPLUGIN_METHOD = {
|
|
31
|
+
next: 'next',
|
|
32
|
+
vite: 'vite',
|
|
33
|
+
astro: 'vite',
|
|
34
|
+
webpack: 'webpack',
|
|
35
|
+
rspack: 'rspack',
|
|
36
|
+
rollup: 'rollup',
|
|
37
|
+
rolldown: 'rolldown',
|
|
38
|
+
esbuild: 'esbuild',
|
|
39
|
+
farm: 'farm',
|
|
40
|
+
bun: 'bun',
|
|
41
|
+
};
|
|
42
|
+
const INSTALL = {
|
|
43
|
+
npm: 'npm i -D',
|
|
44
|
+
pnpm: 'pnpm add -D',
|
|
45
|
+
yarn: 'yarn add -D',
|
|
46
|
+
bun: 'bun add -d',
|
|
47
|
+
};
|
|
48
|
+
const rejectedSpelling = (spelling) => spelling === 'both'
|
|
49
|
+
? undefined
|
|
50
|
+
: spelling === 'logical'
|
|
51
|
+
? 'physical'
|
|
52
|
+
: 'logical';
|
|
53
|
+
exports.rejectedSpelling = rejectedSpelling;
|
|
54
|
+
const compilerOption = (answers) => {
|
|
55
|
+
const rejected = (0, exports.rejectedSpelling)(answers.spelling);
|
|
56
|
+
if (!rejected)
|
|
57
|
+
return undefined;
|
|
58
|
+
const name = rejected === 'logical'
|
|
59
|
+
? 'withoutLogicalProperties'
|
|
60
|
+
: 'withoutPhysicalProperties';
|
|
61
|
+
return `${name}: ${answers.sizes ? '{ sizes: true }' : 'true'}`;
|
|
62
|
+
};
|
|
63
|
+
exports.compilerOption = compilerOption;
|
|
64
|
+
const spellingRule = (answers) => {
|
|
65
|
+
const rejected = (0, exports.rejectedSpelling)(answers.spelling);
|
|
66
|
+
if (!rejected)
|
|
67
|
+
return undefined;
|
|
68
|
+
const name = `'@plumeria/no-${rejected}-properties'`;
|
|
69
|
+
return answers.sizes
|
|
70
|
+
? `${name}: ['error', { sizes: true }]`
|
|
71
|
+
: `${name}: 'error'`;
|
|
72
|
+
};
|
|
73
|
+
exports.spellingRule = spellingRule;
|
|
74
|
+
const pluginOptions = (answers) => {
|
|
75
|
+
const entries = [];
|
|
76
|
+
if (answers.styleProp !== exports.DEFAULT_STYLE_PROP) {
|
|
77
|
+
entries.push(`styleProp: '${answers.styleProp}'`);
|
|
78
|
+
}
|
|
79
|
+
const policy = (0, exports.compilerOption)(answers);
|
|
80
|
+
if (policy)
|
|
81
|
+
entries.push(policy);
|
|
82
|
+
return entries.length === 0 ? '' : `{ ${entries.join(', ')} }`;
|
|
83
|
+
};
|
|
84
|
+
exports.pluginOptions = pluginOptions;
|
|
85
|
+
const pluginCall = (bundler, answers, local = 'plumeria') => `${local}.${UNPLUGIN_METHOD[bundler]}(${(0, exports.pluginOptions)(answers)})`;
|
|
86
|
+
exports.pluginCall = pluginCall;
|
|
87
|
+
const declarationSource = (answers) => {
|
|
88
|
+
if (answers.styleProp === exports.DEFAULT_STYLE_PROP) {
|
|
89
|
+
return `/// <reference types="@plumeria/core/class-style" />\n`;
|
|
90
|
+
}
|
|
91
|
+
return `import type { Style } from '@plumeria/core';
|
|
92
|
+
|
|
93
|
+
declare global {
|
|
94
|
+
namespace React {
|
|
95
|
+
interface HTMLAttributes<T> {
|
|
96
|
+
${answers.styleProp}?: Style;
|
|
97
|
+
}
|
|
98
|
+
interface SVGAttributes<T> {
|
|
99
|
+
${answers.styleProp}?: Style;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
};
|
|
105
|
+
exports.declarationSource = declarationSource;
|
|
106
|
+
const nextOptions = (answers) => {
|
|
107
|
+
const options = (0, exports.pluginOptions)(answers);
|
|
108
|
+
return options === '' ? '' : `, ${options}`;
|
|
109
|
+
};
|
|
110
|
+
const bundlerSource = (bundler, answers, typescript) => {
|
|
111
|
+
if (bundler === 'next') {
|
|
112
|
+
return typescript
|
|
113
|
+
? `import type { NextConfig } from 'next';
|
|
114
|
+
import { withPlumeria } from '@plumeria/next-plugin';
|
|
115
|
+
|
|
116
|
+
const nextConfig: NextConfig = {};
|
|
117
|
+
|
|
118
|
+
export default withPlumeria(nextConfig${nextOptions(answers)});
|
|
119
|
+
`
|
|
120
|
+
: `import { withPlumeria } from '@plumeria/next-plugin';
|
|
121
|
+
|
|
122
|
+
export default withPlumeria({}${nextOptions(answers)});
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
if (bundler === 'vite') {
|
|
126
|
+
return `import { defineConfig } from 'vite';
|
|
127
|
+
import plumeria from '@plumeria/unplugin';
|
|
128
|
+
|
|
129
|
+
export default defineConfig({
|
|
130
|
+
plugins: [${(0, exports.pluginCall)(bundler, answers)}],
|
|
131
|
+
});
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
if (bundler === 'astro') {
|
|
135
|
+
return `import { defineConfig } from 'astro/config';
|
|
136
|
+
import plumeria from '@plumeria/unplugin';
|
|
137
|
+
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
vite: {
|
|
140
|
+
plugins: [${(0, exports.pluginCall)(bundler, answers)}],
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
};
|
|
147
|
+
exports.bundlerSource = bundlerSource;
|
|
148
|
+
const eslintSource = (answers, typescript) => {
|
|
149
|
+
const rules = [
|
|
150
|
+
answers.expandBorderShorthands
|
|
151
|
+
? `'@plumeria/expand-border-shorthands': 'warn'`
|
|
152
|
+
: undefined,
|
|
153
|
+
(0, exports.spellingRule)(answers),
|
|
154
|
+
].filter((rule) => rule !== undefined);
|
|
155
|
+
const block = [
|
|
156
|
+
` {`,
|
|
157
|
+
` files: ['**/*.{${typescript ? 'ts,tsx' : 'js,jsx'}}'],`,
|
|
158
|
+
typescript ? ` languageOptions: {` : undefined,
|
|
159
|
+
typescript ? ` parserOptions: { projectService: true },` : undefined,
|
|
160
|
+
typescript ? ` },` : undefined,
|
|
161
|
+
rules.length > 0 ? ` rules: {` : undefined,
|
|
162
|
+
...rules.map((rule) => ` ${rule},`),
|
|
163
|
+
rules.length > 0 ? ` },` : undefined,
|
|
164
|
+
` },`,
|
|
165
|
+
].filter((line) => line !== undefined);
|
|
166
|
+
const head = [
|
|
167
|
+
`import eslint from '@eslint/js';`,
|
|
168
|
+
typescript ? `import tseslint from 'typescript-eslint';` : undefined,
|
|
169
|
+
`import { defineConfig } from 'eslint/config';`,
|
|
170
|
+
`import plumeria from '@plumeria/eslint-plugin';`,
|
|
171
|
+
].filter((line) => line !== undefined);
|
|
172
|
+
const configs = [
|
|
173
|
+
` eslint.configs.recommended,`,
|
|
174
|
+
typescript ? ` tseslint.configs.recommended,` : undefined,
|
|
175
|
+
` plumeria.configs.recommended,`,
|
|
176
|
+
].filter((line) => line !== undefined);
|
|
177
|
+
return `${head.join('\n')}
|
|
178
|
+
|
|
179
|
+
export default defineConfig(
|
|
180
|
+
${configs.join('\n')}
|
|
181
|
+
${block.join('\n')}
|
|
182
|
+
);
|
|
183
|
+
`;
|
|
184
|
+
};
|
|
185
|
+
exports.eslintSource = eslintSource;
|
|
186
|
+
const eslintEntries = (answers, typescript) => {
|
|
187
|
+
const rules = [
|
|
188
|
+
answers.expandBorderShorthands
|
|
189
|
+
? `'@plumeria/expand-border-shorthands': 'warn'`
|
|
190
|
+
: undefined,
|
|
191
|
+
(0, exports.spellingRule)(answers),
|
|
192
|
+
].filter((rule) => rule !== undefined);
|
|
193
|
+
const entries = ['plumeria.configs.recommended'];
|
|
194
|
+
if (rules.length > 0) {
|
|
195
|
+
entries.push([
|
|
196
|
+
`{`,
|
|
197
|
+
` files: ['**/*.{${typescript ? 'ts,tsx' : 'js,jsx'}}'],`,
|
|
198
|
+
` rules: {`,
|
|
199
|
+
...rules.map((rule) => ` ${rule},`),
|
|
200
|
+
` },`,
|
|
201
|
+
`}`,
|
|
202
|
+
].join('\n'));
|
|
203
|
+
}
|
|
204
|
+
return entries;
|
|
205
|
+
};
|
|
206
|
+
exports.eslintEntries = eslintEntries;
|
|
207
|
+
const packages = (detected, answers) => {
|
|
208
|
+
const present = (0, detect_1.installed)(detected.manifest);
|
|
209
|
+
const wanted = [
|
|
210
|
+
'@plumeria/core',
|
|
211
|
+
detected.bundler === 'next'
|
|
212
|
+
? '@plumeria/next-plugin'
|
|
213
|
+
: '@plumeria/unplugin',
|
|
214
|
+
];
|
|
215
|
+
if (answers.eslint) {
|
|
216
|
+
wanted.push('@plumeria/eslint-plugin', 'oxlint');
|
|
217
|
+
if (!detected.eslintConfig) {
|
|
218
|
+
wanted.push('eslint', '@eslint/js');
|
|
219
|
+
if (detected.typescript)
|
|
220
|
+
wanted.push('typescript-eslint');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (detected.bundler === 'next' &&
|
|
224
|
+
Object.keys((0, exports.cleanScripts)(detected.manifest.scripts ?? {})).length > 0) {
|
|
225
|
+
wanted.push('rimraf');
|
|
226
|
+
}
|
|
227
|
+
return wanted.filter((name) => !(name in present));
|
|
228
|
+
};
|
|
229
|
+
exports.packages = packages;
|
|
230
|
+
const installCommand = (manager, names) => `${INSTALL[manager]} ${names.join(' ')}`;
|
|
231
|
+
exports.installCommand = installCommand;
|
|
232
|
+
const indentOfJson = (source) => {
|
|
233
|
+
const match = /\n(\s+)"/.exec(source);
|
|
234
|
+
return match ? match[1].length : 2;
|
|
235
|
+
};
|
|
236
|
+
const buildScript = (existing) => `plumerialint -- ${existing}`;
|
|
237
|
+
exports.buildScript = buildScript;
|
|
238
|
+
const plan = (detected, answers) => {
|
|
239
|
+
const actions = [];
|
|
240
|
+
const names = (0, exports.packages)(detected, answers);
|
|
241
|
+
if (names.length > 0) {
|
|
242
|
+
actions.push({
|
|
243
|
+
kind: 'install',
|
|
244
|
+
label: 'install',
|
|
245
|
+
note: names.join(' '),
|
|
246
|
+
command: (0, exports.installCommand)(detected.packageManager, names),
|
|
247
|
+
packages: names,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (detected.declaration) {
|
|
251
|
+
actions.push({
|
|
252
|
+
kind: 'skip',
|
|
253
|
+
label: 'skip',
|
|
254
|
+
note: 'already declares the styling prop',
|
|
255
|
+
file: detected.declaration,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
actions.push({
|
|
260
|
+
kind: 'write',
|
|
261
|
+
label: 'write',
|
|
262
|
+
note: answers.styleProp,
|
|
263
|
+
file: 'plumeria.d.ts',
|
|
264
|
+
contents: (0, exports.declarationSource)(answers),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
actions.push(bundlerAction(detected, answers));
|
|
268
|
+
if (answers.eslint)
|
|
269
|
+
actions.push(eslintAction(detected, answers));
|
|
270
|
+
if (answers.eslint || detected.bundler === 'next')
|
|
271
|
+
actions.push(scriptAction(detected, answers));
|
|
272
|
+
return actions;
|
|
273
|
+
};
|
|
274
|
+
exports.plan = plan;
|
|
275
|
+
const read = (detected, file) => node_fs_1.default.readFileSync(node_path_1.default.join(detected.root, file), 'utf8');
|
|
276
|
+
const bundlerAction = (detected, answers) => {
|
|
277
|
+
const { bundler, bundlerConfig, typescript } = detected;
|
|
278
|
+
const call = (0, exports.pluginCall)(bundler, answers);
|
|
279
|
+
if (!bundlerConfig) {
|
|
280
|
+
const fresh = (0, exports.bundlerSource)(bundler, answers, typescript);
|
|
281
|
+
const file = (0, detect_1.defaultConfigName)(bundler, typescript);
|
|
282
|
+
if (!fresh) {
|
|
283
|
+
return {
|
|
284
|
+
kind: 'manual',
|
|
285
|
+
label: 'manual',
|
|
286
|
+
note: bundler === 'esbuild' || bundler === 'bun'
|
|
287
|
+
? 'add the plugin to the build script'
|
|
288
|
+
: `no ${bundler} config found`,
|
|
289
|
+
file,
|
|
290
|
+
snippet: snippetFor(bundler, call),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
kind: 'write',
|
|
295
|
+
label: 'write',
|
|
296
|
+
note: bundler,
|
|
297
|
+
file,
|
|
298
|
+
contents: fresh,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const source = read(detected, bundlerConfig);
|
|
302
|
+
const kind = (0, source_1.moduleKindOf)(bundlerConfig, source);
|
|
303
|
+
if (bundler === 'next') {
|
|
304
|
+
const { names, rest } = (0, source_1.importedFrom)(source, '@plumeria/next-plugin');
|
|
305
|
+
const local = names.find((name) => (0, source_1.callsFunction)(rest, name));
|
|
306
|
+
if (local) {
|
|
307
|
+
return {
|
|
308
|
+
kind: 'skip',
|
|
309
|
+
label: 'skip',
|
|
310
|
+
note: 'withPlumeria is already applied',
|
|
311
|
+
file: bundlerConfig,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const bound = names[0];
|
|
315
|
+
const wrapped = (0, source_1.wrapDefaultExport)(bound
|
|
316
|
+
? source
|
|
317
|
+
: (0, source_1.addImport)(source, NAMED_IMPORTS[kind]('withPlumeria', '@plumeria/next-plugin')), bound ?? 'withPlumeria', nextOptions(answers));
|
|
318
|
+
return wrapped
|
|
319
|
+
? {
|
|
320
|
+
kind: 'patch',
|
|
321
|
+
label: 'patch',
|
|
322
|
+
note: `${bound ?? 'withPlumeria'}(...)`,
|
|
323
|
+
file: bundlerConfig,
|
|
324
|
+
contents: wrapped,
|
|
325
|
+
}
|
|
326
|
+
: {
|
|
327
|
+
kind: 'manual',
|
|
328
|
+
label: 'manual',
|
|
329
|
+
note: 'no default export to wrap',
|
|
330
|
+
file: bundlerConfig,
|
|
331
|
+
snippet: snippetFor(bundler, call),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const { names, rest } = (0, source_1.importedFrom)(source, '@plumeria/unplugin');
|
|
335
|
+
const used = names.find((name) => (0, source_1.callsMethod)(rest, name));
|
|
336
|
+
if (used) {
|
|
337
|
+
return {
|
|
338
|
+
kind: 'skip',
|
|
339
|
+
label: 'skip',
|
|
340
|
+
note: 'the plugin is already registered',
|
|
341
|
+
file: bundlerConfig,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const bound = names[0];
|
|
345
|
+
const entry = (0, exports.pluginCall)(bundler, answers, bound ?? 'plumeria');
|
|
346
|
+
const imported = bound
|
|
347
|
+
? source
|
|
348
|
+
: (0, source_1.addImport)(source, IMPORTS[kind]('plumeria', '@plumeria/unplugin'));
|
|
349
|
+
const at = (0, source_1.pluginsArrayOf)(imported);
|
|
350
|
+
const patched = (at === undefined ? undefined : (0, source_1.appendAtArray)(imported, at, entry)) ??
|
|
351
|
+
(bundler === 'astro' ? addViteBlock(imported, entry) : undefined);
|
|
352
|
+
return patched
|
|
353
|
+
? {
|
|
354
|
+
kind: 'patch',
|
|
355
|
+
label: 'patch',
|
|
356
|
+
note: entry,
|
|
357
|
+
file: bundlerConfig,
|
|
358
|
+
contents: patched,
|
|
359
|
+
}
|
|
360
|
+
: {
|
|
361
|
+
kind: 'manual',
|
|
362
|
+
label: 'manual',
|
|
363
|
+
note: 'no plugins array of its own to extend',
|
|
364
|
+
file: bundlerConfig,
|
|
365
|
+
snippet: snippetFor(bundler, call),
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
const IMPORTS = {
|
|
369
|
+
esm: (name, specifier) => `import ${name} from '${specifier}';`,
|
|
370
|
+
cjs: (name, specifier) => `const ${name} = require('${specifier}').default;`,
|
|
371
|
+
};
|
|
372
|
+
const NAMED_IMPORTS = {
|
|
373
|
+
esm: (name, specifier) => `import { ${name} } from '${specifier}';`,
|
|
374
|
+
cjs: (name, specifier) => `const { ${name} } = require('${specifier}');`,
|
|
375
|
+
};
|
|
376
|
+
const addViteBlock = (source, call) => {
|
|
377
|
+
const match = /\bdefineConfig\s*\(/.exec(source);
|
|
378
|
+
if (!match)
|
|
379
|
+
return undefined;
|
|
380
|
+
const open = source.indexOf('{', match.index + match[0].length - 1);
|
|
381
|
+
if (open === -1 || open > (0, source_1.closerOf)(source, match.index + match[0].length - 1))
|
|
382
|
+
return undefined;
|
|
383
|
+
return (0, source_1.appendInside)(source, open, [`vite: {\n plugins: [${call}],\n}`]);
|
|
384
|
+
};
|
|
385
|
+
const snippetFor = (bundler, call) => {
|
|
386
|
+
if (bundler === 'next') {
|
|
387
|
+
return `import { withPlumeria } from '@plumeria/next-plugin';\n\nexport default withPlumeria(nextConfig);`;
|
|
388
|
+
}
|
|
389
|
+
const imported = `import plumeria from '@plumeria/unplugin';`;
|
|
390
|
+
if (bundler === 'astro') {
|
|
391
|
+
return `${imported}\n\nvite: {\n plugins: [${call}],\n}`;
|
|
392
|
+
}
|
|
393
|
+
return `${imported}\n\nplugins: [${call}]`;
|
|
394
|
+
};
|
|
395
|
+
const eslintAction = (detected, answers) => {
|
|
396
|
+
const { eslintConfig, typescript } = detected;
|
|
397
|
+
const entries = (0, exports.eslintEntries)(answers, typescript);
|
|
398
|
+
if (!eslintConfig) {
|
|
399
|
+
return {
|
|
400
|
+
kind: 'write',
|
|
401
|
+
label: 'write',
|
|
402
|
+
note: 'plumeria.configs.recommended',
|
|
403
|
+
file: typescript ? 'eslint.config.ts' : 'eslint.config.mjs',
|
|
404
|
+
contents: (0, exports.eslintSource)(answers, typescript),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (!eslintConfig.startsWith('eslint.config.')) {
|
|
408
|
+
return {
|
|
409
|
+
kind: 'manual',
|
|
410
|
+
label: 'manual',
|
|
411
|
+
note: 'flat config only',
|
|
412
|
+
file: eslintConfig,
|
|
413
|
+
snippet: `{\n "plugins": ["@plumeria"],\n "extends": ["plugin:@plumeria/recommended"]\n}`,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const source = read(detected, eslintConfig);
|
|
417
|
+
const { names, rest } = (0, source_1.importedFrom)(source, '@plumeria/eslint-plugin');
|
|
418
|
+
const used = names.find((name) => (0, source_1.readsMember)(rest, name, 'configs'));
|
|
419
|
+
if (used) {
|
|
420
|
+
return {
|
|
421
|
+
kind: 'skip',
|
|
422
|
+
label: 'skip',
|
|
423
|
+
note: 'the plugin is already configured',
|
|
424
|
+
file: eslintConfig,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const bound = names[0] ?? 'plumeria';
|
|
428
|
+
const patched = (0, source_1.appendToConfigList)(names.length > 0
|
|
429
|
+
? source
|
|
430
|
+
: (0, source_1.addImport)(source, (0, source_1.moduleKindOf)(eslintConfig, source) === 'cjs'
|
|
431
|
+
? `const plumeria = require('@plumeria/eslint-plugin');`
|
|
432
|
+
: `import plumeria from '@plumeria/eslint-plugin';`), entries.map((entry) => entry.replace(/^plumeria\.configs\b/, `${bound}.configs`)));
|
|
433
|
+
return patched
|
|
434
|
+
? {
|
|
435
|
+
kind: 'patch',
|
|
436
|
+
label: 'patch',
|
|
437
|
+
note: `${bound}.configs.recommended`,
|
|
438
|
+
file: eslintConfig,
|
|
439
|
+
contents: patched,
|
|
440
|
+
}
|
|
441
|
+
: {
|
|
442
|
+
kind: 'manual',
|
|
443
|
+
label: 'manual',
|
|
444
|
+
note: 'no config list to extend',
|
|
445
|
+
file: eslintConfig,
|
|
446
|
+
snippet: entries.join(',\n'),
|
|
447
|
+
};
|
|
448
|
+
};
|
|
449
|
+
exports.CLEAN_SCRIPT = 'rimraf .next';
|
|
450
|
+
const cleanScripts = (scripts) => {
|
|
451
|
+
const added = {};
|
|
452
|
+
for (const name of ['dev', 'build']) {
|
|
453
|
+
if (scripts[name] === undefined)
|
|
454
|
+
continue;
|
|
455
|
+
if (scripts[`pre${name}`] === undefined)
|
|
456
|
+
added[`pre${name}`] = exports.CLEAN_SCRIPT;
|
|
457
|
+
}
|
|
458
|
+
return added;
|
|
459
|
+
};
|
|
460
|
+
exports.cleanScripts = cleanScripts;
|
|
461
|
+
const scriptAction = (detected, answers) => {
|
|
462
|
+
const source = read(detected, 'package.json');
|
|
463
|
+
const manifest = JSON.parse(source);
|
|
464
|
+
const scripts = { ...manifest.scripts };
|
|
465
|
+
const notes = [];
|
|
466
|
+
const existing = scripts.build;
|
|
467
|
+
if (answers.eslint && existing && !existing.includes('plumerialint')) {
|
|
468
|
+
scripts.build = (0, exports.buildScript)(existing);
|
|
469
|
+
notes.push(`build: ${scripts.build}`);
|
|
470
|
+
}
|
|
471
|
+
if (detected.bundler === 'next') {
|
|
472
|
+
const added = (0, exports.cleanScripts)(scripts);
|
|
473
|
+
for (const [name, script] of Object.entries(added)) {
|
|
474
|
+
scripts[name] = script;
|
|
475
|
+
notes.push(`${name}: ${script}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (notes.length === 0) {
|
|
479
|
+
return {
|
|
480
|
+
kind: 'skip',
|
|
481
|
+
label: 'skip',
|
|
482
|
+
note: answers.eslint && !existing
|
|
483
|
+
? 'no build script to guard'
|
|
484
|
+
: 'the scripts already carry it',
|
|
485
|
+
file: 'package.json',
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
kind: 'patch',
|
|
490
|
+
label: 'patch',
|
|
491
|
+
note: notes.join(', '),
|
|
492
|
+
file: 'package.json',
|
|
493
|
+
contents: `${JSON.stringify({ ...manifest, scripts }, null, indentOfJson(source))}\n`,
|
|
494
|
+
};
|
|
495
|
+
};
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const closerOf: (source: string, open: number) => number;
|
|
2
|
+
export declare const importsModule: (source: string, specifier: string) => boolean;
|
|
3
|
+
export declare const addImport: (source: string, statement: string) => string;
|
|
4
|
+
export declare const appendToArray: (source: string, key: string, entry: string) => string | undefined;
|
|
5
|
+
export declare const appendAtArray: (source: string, open: number, entry: string) => string | undefined;
|
|
6
|
+
export declare const appendToCall: (source: string, callee: string, entries: readonly string[]) => string | undefined;
|
|
7
|
+
export declare const appendInside: (source: string, open: number, entries: readonly string[]) => string | undefined;
|
|
8
|
+
export interface DefaultExport {
|
|
9
|
+
keyword: string;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
expression: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const defaultExportOf: (source: string) => DefaultExport | undefined;
|
|
15
|
+
export declare const wrapDefaultExport: (source: string, wrapper: string, extra?: string) => string | undefined;
|
|
16
|
+
export declare const appendToConfigList: (source: string, entries: readonly string[]) => string | undefined;
|
|
17
|
+
export type ModuleKind = 'esm' | 'cjs';
|
|
18
|
+
export declare const moduleKindOf: (file: string, source: string) => ModuleKind;
|
|
19
|
+
export declare const directProperty: (source: string, open: number, key: string) => number | undefined;
|
|
20
|
+
export declare const configObjects: (source: string) => number[];
|
|
21
|
+
export declare const pluginsArrayOf: (source: string) => number | undefined;
|
|
22
|
+
export interface Imported {
|
|
23
|
+
names: string[];
|
|
24
|
+
rest: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const codeMask: (source: string) => string;
|
|
27
|
+
export declare const importedFrom: (source: string, specifier: string) => Imported;
|
|
28
|
+
export declare const callsMethod: (source: string, name: string) => boolean;
|
|
29
|
+
export declare const callsFunction: (source: string, name: string) => boolean;
|
|
30
|
+
export declare const readsMember: (source: string, name: string, member: string) => boolean;
|