create-next-mui 0.1.3 → 0.1.4
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 +8 -0
- package/package.json +1 -1
- package/src/index.js +178 -61
package/README.md
CHANGED
|
@@ -75,6 +75,9 @@ Current prompts include:
|
|
|
75
75
|
- Language selection
|
|
76
76
|
- TypeScript (recommended)
|
|
77
77
|
- JavaScript
|
|
78
|
+
- Features
|
|
79
|
+
- Zustand
|
|
80
|
+
- React Query ( TanStack Query )
|
|
78
81
|
|
|
79
82
|
---
|
|
80
83
|
|
|
@@ -101,6 +104,11 @@ Includes:
|
|
|
101
104
|
- Material UI
|
|
102
105
|
- ESLint Flat Config
|
|
103
106
|
|
|
107
|
+
## Features
|
|
108
|
+
|
|
109
|
+
- Zustand
|
|
110
|
+
- React Query ( TanStack Query )
|
|
111
|
+
|
|
104
112
|
---
|
|
105
113
|
|
|
106
114
|
# Generated Project Structure
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
12
12
|
const __dirname = path.dirname(__filename);
|
|
13
13
|
|
|
14
14
|
const args = process.argv.slice(2);
|
|
15
|
+
const command = args[0];
|
|
16
|
+
const featureName = args[1];
|
|
15
17
|
|
|
16
18
|
const positionalArgs = args.filter(
|
|
17
19
|
(arg) => !["--yes", "-y", "--js"].includes(arg),
|
|
@@ -53,6 +55,8 @@ const FEATURES = [
|
|
|
53
55
|
},
|
|
54
56
|
];
|
|
55
57
|
|
|
58
|
+
// ----------------------------------------------------------------------
|
|
59
|
+
// Helper Functions
|
|
56
60
|
// ----------------------------------------------------------------------
|
|
57
61
|
|
|
58
62
|
function validateProjectName(value) {
|
|
@@ -71,31 +75,126 @@ function getPackageName(projectName, targetProjectDir) {
|
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
async function updateProjectName(targetProjectDir, packageName) {
|
|
74
|
-
const
|
|
78
|
+
const filePath = path.join(targetProjectDir, "package.json");
|
|
79
|
+
try {
|
|
80
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
81
|
+
const json = JSON.parse(content);
|
|
82
|
+
json.name = packageName;
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
if (json.packages?.[""]?.name) {
|
|
85
|
+
json.packages[""].name = packageName;
|
|
86
|
+
}
|
|
87
|
+
await fs.writeFile(filePath, `${JSON.stringify(json, null, 2)}\n`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error.code === "ENOENT") return;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
async function mergePackageJson(targetProjectDir, manifest) {
|
|
95
|
+
const filePath = path.join(targetProjectDir, "package.json");
|
|
96
|
+
try {
|
|
97
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
98
|
+
const json = JSON.parse(content);
|
|
99
|
+
|
|
100
|
+
json.dependencies = {
|
|
101
|
+
...json.dependencies,
|
|
102
|
+
...(manifest.dependencies || {}),
|
|
103
|
+
};
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
json.devDependencies = {
|
|
106
|
+
...json.devDependencies,
|
|
107
|
+
...(manifest.devDependencies || {}),
|
|
108
|
+
};
|
|
85
109
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
110
|
+
await fs.writeFile(filePath, `${JSON.stringify(json, null, 2)}\n`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error.code === "ENOENT") return;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
89
116
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
117
|
+
async function injectProviderToLayout(targetProjectDir, manifest, language) {
|
|
118
|
+
const ext = language === "ts" ? "tsx" : "jsx";
|
|
119
|
+
const layoutPath = path.join(targetProjectDir, "src", "app", `layout.${ext}`);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
let content = await fs.readFile(layoutPath, "utf8");
|
|
123
|
+
|
|
124
|
+
// 1. Inject the Import Line safely below the last import
|
|
125
|
+
if (manifest.importLine && !content.includes(manifest.importLine)) {
|
|
126
|
+
const lines = content.split("\n");
|
|
127
|
+
const lastImportIndex = lines.findLastIndex((line) =>
|
|
128
|
+
line.trim().startsWith("import"),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (lastImportIndex !== -1) {
|
|
132
|
+
lines.splice(lastImportIndex + 1, 0, manifest.importLine);
|
|
133
|
+
content = lines.join("\n");
|
|
134
|
+
} else {
|
|
135
|
+
content = `${manifest.importLine}\n${content}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. Wrap {children} dynamically
|
|
140
|
+
if (manifest.expression) {
|
|
141
|
+
const openTag = manifest.expression.open;
|
|
142
|
+
const closeTag = manifest.expression.close;
|
|
143
|
+
|
|
144
|
+
if (!content.includes(openTag)) {
|
|
145
|
+
content = content.replace(
|
|
146
|
+
/(\>\s*)\{\s*children\s*\}(\s*\<)/,
|
|
147
|
+
`$1${openTag} {children} ${closeTag}$2`,
|
|
148
|
+
);
|
|
94
149
|
}
|
|
95
|
-
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await fs.writeFile(layoutPath, content, "utf8");
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code === "ENOENT") return;
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function applyFeature(feature, targetProjectDir, language) {
|
|
160
|
+
const featureDir = path.resolve(
|
|
161
|
+
__dirname,
|
|
162
|
+
"..",
|
|
163
|
+
"next-mui-features",
|
|
164
|
+
feature,
|
|
96
165
|
);
|
|
166
|
+
const manifestPath = path.join(featureDir, "manifest.json");
|
|
167
|
+
const ext = language == "ts" ? "tsx" : "jsx";
|
|
168
|
+
|
|
169
|
+
let manifest = {};
|
|
170
|
+
try {
|
|
171
|
+
const manifestContent = await fs.readFile(manifestPath, "utf8");
|
|
172
|
+
manifest = JSON.parse(manifestContent);
|
|
173
|
+
} catch {
|
|
174
|
+
// Fail-safe if manifest is missing or malformed
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 1. Copy feature-specific files from the language subdirectory
|
|
179
|
+
if (manifest.files) {
|
|
180
|
+
for (const file of manifest.files) {
|
|
181
|
+
const srcPath = path.resolve(featureDir, language, `${file.src}.${ext}`);
|
|
182
|
+
const destPath = path.resolve(targetProjectDir, `${file.dest}.${ext}`);
|
|
183
|
+
|
|
184
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
185
|
+
await fs.copyFile(srcPath, destPath);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 2. Merge dependencies into package.json
|
|
190
|
+
await mergePackageJson(targetProjectDir, manifest);
|
|
191
|
+
|
|
192
|
+
// 3. Perform dynamic layout file nesting
|
|
193
|
+
await injectProviderToLayout(targetProjectDir, manifest, language);
|
|
97
194
|
}
|
|
98
195
|
|
|
196
|
+
// ----------------------------------------------------------------------
|
|
197
|
+
// Main Execution Control Loop
|
|
99
198
|
// ----------------------------------------------------------------------
|
|
100
199
|
|
|
101
200
|
async function main() {
|
|
@@ -103,6 +202,65 @@ async function main() {
|
|
|
103
202
|
|
|
104
203
|
p.intro(color.bgBlue(color.white(" create-next-mui ")));
|
|
105
204
|
|
|
205
|
+
// ----------------------------------------------------------------------
|
|
206
|
+
// Add Feature Command Pipeline
|
|
207
|
+
// ----------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
if (command === "add") {
|
|
210
|
+
if (!featureName) {
|
|
211
|
+
p.cancel(
|
|
212
|
+
"Please specify a feature.\n\nExample:\ncreate-next-mui add react-query",
|
|
213
|
+
);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!FEATURES.some((f) => f.value === featureName)) {
|
|
218
|
+
p.cancel(`Unknown feature "${featureName}".`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const projectRoot = process.cwd();
|
|
223
|
+
|
|
224
|
+
// Verify package.json exists in target execution environment
|
|
225
|
+
try {
|
|
226
|
+
await fs.access(path.join(projectRoot, "package.json"));
|
|
227
|
+
} catch {
|
|
228
|
+
p.cancel(
|
|
229
|
+
"No package.json found.\nRun this command inside a Next.js project.",
|
|
230
|
+
);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Auto-detect layout flavor via tsconfig existence
|
|
235
|
+
let language = "js";
|
|
236
|
+
try {
|
|
237
|
+
await fs.access(path.join(projectRoot, "tsconfig.json"));
|
|
238
|
+
language = "ts";
|
|
239
|
+
} catch {
|
|
240
|
+
// Keep fallback as js configuration
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const spinner = p.spinner();
|
|
244
|
+
spinner.start(`Installing ${featureName}...`);
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
await applyFeature(featureName, projectRoot, language);
|
|
248
|
+
|
|
249
|
+
spinner.stop(color.green(`${featureName} installed successfully.`));
|
|
250
|
+
p.note(`${color.cyan("npm install")}`, "Next Step");
|
|
251
|
+
p.outro(`✨ ${featureName} added successfully!`);
|
|
252
|
+
return;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
spinner.stop(color.red("Failed to install feature."));
|
|
255
|
+
p.note(error.message, "Error");
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ----------------------------------------------------------------------
|
|
261
|
+
// Interactive Fresh Workspace Scaffolding Pipeline
|
|
262
|
+
// ----------------------------------------------------------------------
|
|
263
|
+
|
|
106
264
|
const projectNameError = hasInitialProjectName
|
|
107
265
|
? validateProjectName(PROJECT_NAME)
|
|
108
266
|
: undefined;
|
|
@@ -148,10 +306,6 @@ async function main() {
|
|
|
148
306
|
},
|
|
149
307
|
);
|
|
150
308
|
|
|
151
|
-
// --------------------------------------------------------------------
|
|
152
|
-
// Paths
|
|
153
|
-
// --------------------------------------------------------------------
|
|
154
|
-
|
|
155
309
|
const baseTemplateDir = path.resolve(
|
|
156
310
|
__dirname,
|
|
157
311
|
"..",
|
|
@@ -160,24 +314,17 @@ async function main() {
|
|
|
160
314
|
);
|
|
161
315
|
|
|
162
316
|
const targetProjectDir = path.resolve(process.cwd(), project.name);
|
|
163
|
-
|
|
164
317
|
const packageName = getPackageName(project.name, targetProjectDir);
|
|
165
318
|
|
|
166
|
-
// --------------------------------------------------------------------
|
|
167
|
-
// Current directory validation
|
|
168
|
-
// --------------------------------------------------------------------
|
|
169
|
-
|
|
170
319
|
if (project.name === ".") {
|
|
171
320
|
try {
|
|
172
321
|
const files = await fs.readdir(targetProjectDir);
|
|
173
|
-
|
|
174
322
|
if (files.length > 0) {
|
|
175
323
|
p.log.error(
|
|
176
324
|
color.red(
|
|
177
325
|
"The current directory is not empty! Please clear it or specify another project name.",
|
|
178
326
|
),
|
|
179
327
|
);
|
|
180
|
-
|
|
181
328
|
process.exit(1);
|
|
182
329
|
}
|
|
183
330
|
} catch {
|
|
@@ -185,64 +332,34 @@ async function main() {
|
|
|
185
332
|
}
|
|
186
333
|
}
|
|
187
334
|
|
|
188
|
-
// --------------------------------------------------------------------
|
|
189
|
-
|
|
190
335
|
const s = p.spinner();
|
|
191
|
-
|
|
192
336
|
s.start("Scaffolding your Next.js + MUI workspace...");
|
|
193
337
|
|
|
194
338
|
try {
|
|
195
|
-
//
|
|
196
|
-
// Copy base template
|
|
197
|
-
// ------------------------------------------------------------------
|
|
198
|
-
|
|
339
|
+
// 1. Unpack base layout system
|
|
199
340
|
await fs.cp(baseTemplateDir, targetProjectDir, {
|
|
200
341
|
recursive: true,
|
|
201
342
|
filter: (src) => {
|
|
202
343
|
const name = path.basename(src);
|
|
203
|
-
|
|
204
344
|
return !["node_modules", ".next", "out", "build"].includes(name);
|
|
205
345
|
},
|
|
206
346
|
});
|
|
207
347
|
|
|
208
|
-
//
|
|
209
|
-
// Apply selected features
|
|
210
|
-
// ------------------------------------------------------------------
|
|
211
|
-
|
|
348
|
+
// 2. Map and loop over chosen features sequentially
|
|
212
349
|
for (const feature of project.features) {
|
|
213
|
-
|
|
214
|
-
__dirname,
|
|
215
|
-
"..",
|
|
216
|
-
"next-mui-features",
|
|
217
|
-
feature,
|
|
218
|
-
project.language,
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
await fs.cp(featureDir, targetProjectDir, {
|
|
222
|
-
recursive: true,
|
|
223
|
-
filter: (src) => {
|
|
224
|
-
const name = path.basename(src);
|
|
225
|
-
|
|
226
|
-
return !["node_modules", ".next", "out", "build"].includes(name);
|
|
227
|
-
},
|
|
228
|
-
});
|
|
350
|
+
await applyFeature(feature, targetProjectDir, project.language);
|
|
229
351
|
}
|
|
230
352
|
|
|
231
|
-
//
|
|
232
|
-
|
|
353
|
+
// 3. Finalize package.json configuration naming
|
|
233
354
|
await updateProjectName(targetProjectDir, packageName);
|
|
234
355
|
|
|
235
356
|
s.stop(color.green("Workspace scaffolded successfully!"));
|
|
236
357
|
} catch (error) {
|
|
237
358
|
s.stop(color.red("Failed to scaffold workspace."));
|
|
238
|
-
|
|
239
359
|
p.note(color.yellow(error.message), "Troubleshooting");
|
|
240
|
-
|
|
241
360
|
process.exit(1);
|
|
242
361
|
}
|
|
243
362
|
|
|
244
|
-
// --------------------------------------------------------------------
|
|
245
|
-
|
|
246
363
|
const cd =
|
|
247
364
|
project.name === "." ? "" : `${color.cyan(`cd ${project.name}`)}\n`;
|
|
248
365
|
|