@yousxlfs/next-arch 0.1.1 → 0.2.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/dist/index.js +580 -84
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/app/package.json +25 -4
- package/templates/packages/better-auth/examples/src/features/_examples/with-better-auth/lib/auth-placeholder.ts +14 -0
- package/templates/packages/env/core/src/shared/config/env.ts +22 -0
- package/templates/packages/jotai/core/src/shared/providers/JotaiProvider.tsx +19 -0
- package/templates/packages/jotai/examples/src/features/_examples/with-jotai/README.md +3 -0
- package/templates/packages/jotai/examples/src/features/_examples/with-jotai/model/example.atoms.ts +19 -0
- package/templates/packages/motion/core/src/shared/lib/motion.ts +17 -0
- package/templates/packages/motion/examples/src/features/_examples/with-motion/components/ExampleMotionCard.tsx +19 -0
- package/templates/packages/next-intl/core/src/shared/config/i18n.ts +10 -0
- package/templates/packages/nuqs/examples/src/features/_examples/with-nuqs/README.md +3 -0
- package/templates/packages/nuqs/examples/src/features/_examples/with-nuqs/hooks/use-example-params.ts +19 -0
- package/templates/packages/react-hook-form/examples/src/features/_examples/with-react-hook-form/README.md +1 -0
- package/templates/packages/react-hook-form/examples/src/features/_examples/with-react-hook-form/components/ExampleRhfForm.tsx +40 -0
- package/templates/packages/redux/core/src/app/providers/redux-store.ts +15 -0
- package/templates/packages/redux/core/src/shared/providers/ReduxProvider.tsx +24 -0
- package/templates/packages/redux/examples/src/app/providers/redux-store.ts +18 -0
- package/templates/packages/redux/examples/src/features/_examples/with-redux/README.md +4 -0
- package/templates/packages/redux/examples/src/features/_examples/with-redux/model/example.slice.ts +36 -0
- package/templates/packages/sentry/core/src/shared/config/sentry.ts +9 -0
- package/templates/packages/sonner/examples/src/features/_examples/with-sonner/lib/toast.ts +7 -0
- package/templates/packages/sonner-provider/core/src/shared/providers/SonnerToaster.tsx +13 -0
- package/templates/packages/tanstack-form/examples/src/features/_examples/with-tanstack-form/README.md +3 -0
- package/templates/packages/tanstack-form/examples/src/features/_examples/with-tanstack-form/components/ExampleForm.tsx +60 -0
- package/templates/packages/tanstack-query/core/src/shared/lib/query-client.ts +24 -0
- package/templates/packages/tanstack-query/core/src/shared/providers/QueryProvider.tsx +30 -0
- package/templates/packages/tanstack-query/examples/src/features/_examples/with-tanstack-query/README.md +34 -0
- package/templates/packages/tanstack-query/examples/src/features/_examples/with-tanstack-query/actions/example.action.ts +34 -0
- package/templates/packages/tanstack-query/examples/src/features/_examples/with-tanstack-query/queries/use-example.query.ts +49 -0
- package/templates/packages/tanstack-table/examples/src/features/_examples/with-tanstack-table/README.md +3 -0
- package/templates/packages/tanstack-table/examples/src/features/_examples/with-tanstack-table/components/ExampleTable.tsx +66 -0
- package/templates/packages/trpc/core/src/app/api/trpc/router.ts +19 -0
- package/templates/packages/trpc/examples/src/app/providers/trpc-client.ts +13 -0
- package/templates/packages/uploadthing/core/src/app/api/uploadthing/route.ts +10 -0
- package/templates/packages/zustand/core/src/shared/lib/store.ts +13 -0
- package/templates/packages/zustand/examples/src/features/_examples/with-zustand/README.md +13 -0
- package/templates/packages/zustand/examples/src/features/_examples/with-zustand/model/example.store.ts +28 -0
- package/templates/pages/auth/src/app/({{name}})/layout.tsx +7 -0
- package/templates/pages/auth/src/app/({{name}})/login/page.tsx +5 -0
- package/templates/pages/auth/src/app/({{name}})/register/page.tsx +5 -0
- package/templates/pages/auth/src/entities/user/index.ts +2 -0
- package/templates/pages/auth/src/entities/user/lib/user-schema.ts +9 -0
- package/templates/pages/auth/src/entities/user/types/user.types.ts +5 -0
- package/templates/pages/auth/src/features/{{name}}/actions/login.action.ts +7 -0
- package/templates/pages/auth/src/features/{{name}}/actions/logout.action.ts +5 -0
- package/templates/pages/auth/src/features/{{name}}/actions/register.action.ts +7 -0
- package/templates/pages/auth/src/features/{{name}}/components/AuthGuard.tsx +14 -0
- package/templates/pages/auth/src/features/{{name}}/components/LoginForm.tsx +36 -0
- package/templates/pages/auth/src/features/{{name}}/components/RegisterForm.tsx +43 -0
- package/templates/pages/auth/src/features/{{name}}/hooks/use-session.ts +14 -0
- package/templates/pages/auth/src/features/{{name}}/index.ts +9 -0
- package/templates/pages/auth/src/features/{{name}}/lib/auth-helpers.ts +3 -0
- package/templates/pages/auth/src/features/{{name}}/queries/use-user.query.ts +16 -0
- package/templates/pages/auth/src/features/{{name}}/types/auth.types.ts +13 -0
- package/templates/pages/auth/src/views/{{name}}/LoginView.tsx +10 -0
- package/templates/pages/auth/src/views/{{name}}/RegisterView.tsx +10 -0
- package/templates/pages/auth/src/views/{{name}}/index.ts +2 -0
- package/templates/pages/blank/src/app/{{name}}/page.tsx +5 -0
- package/templates/pages/blank/src/features/{{name}}/index.ts +3 -0
- package/templates/pages/blank/src/views/{{name}}/index.ts +1 -0
- package/templates/pages/blank/src/views/{{name}}/{{Name}}View.tsx +8 -0
- package/templates/pages/crud/src/app/{{name}}/[id]/page.tsx +5 -0
- package/templates/pages/crud/src/app/{{name}}/new/page.tsx +5 -0
- package/templates/pages/crud/src/app/{{name}}/page.tsx +5 -0
- package/templates/pages/crud/src/entities/{{name}}/index.ts +2 -0
- package/templates/pages/crud/src/entities/{{name}}/lib/{{name}}-schema.ts +6 -0
- package/templates/pages/crud/src/entities/{{name}}/types/{{name}}.types.ts +4 -0
- package/templates/pages/crud/src/features/{{name}}/actions/create-{{name}}.action.ts +5 -0
- package/templates/pages/crud/src/features/{{name}}/actions/delete-{{name}}.action.ts +5 -0
- package/templates/pages/crud/src/features/{{name}}/actions/update-{{name}}.action.ts +5 -0
- package/templates/pages/crud/src/features/{{name}}/components/ProductCard.tsx +3 -0
- package/templates/pages/crud/src/features/{{name}}/components/ProductForm.tsx +5 -0
- package/templates/pages/crud/src/features/{{name}}/components/ProductsList.tsx +15 -0
- package/templates/pages/crud/src/features/{{name}}/index.ts +8 -0
- package/templates/pages/crud/src/features/{{name}}/queries/use-{{name}}.query.ts +10 -0
- package/templates/pages/crud/src/features/{{name}}/queries/use-{{name}}s.query.ts +10 -0
- package/templates/pages/crud/src/views/{{name}}/index.ts +1 -0
- package/templates/pages/crud/src/views/{{name}}/{{Name}}ListView.tsx +26 -0
- package/templates/pages/dashboard/src/app/{{name}}/layout.tsx +8 -0
- package/templates/pages/dashboard/src/app/{{name}}/page.tsx +5 -0
- package/templates/pages/dashboard/src/features/{{name}}/components/AnalyticsCard.tsx +8 -0
- package/templates/pages/dashboard/src/features/{{name}}/index.ts +1 -0
- package/templates/pages/dashboard/src/views/{{name}}/DashboardView.tsx +10 -0
- package/templates/pages/dashboard/src/views/{{name}}/index.ts +1 -0
- package/templates/pages/profile/src/app/{{name}}/page.tsx +5 -0
- package/templates/pages/profile/src/features/{{name}}/components/ProfileCard.tsx +8 -0
- package/templates/pages/profile/src/features/{{name}}/index.ts +1 -0
- package/templates/pages/profile/src/views/{{name}}/ProfileView.tsx +9 -0
- package/templates/pages/profile/src/views/{{name}}/index.ts +1 -0
- package/templates/pages/settings/src/app/{{name}}/page.tsx +5 -0
- package/templates/pages/settings/src/features/{{name}}/components/SettingsTabs.tsx +18 -0
- package/templates/pages/settings/src/features/{{name}}/index.ts +1 -0
- package/templates/pages/settings/src/views/{{name}}/SettingsView.tsx +10 -0
- package/templates/pages/settings/src/views/{{name}}/index.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
|
-
import { cancel, log as
|
|
5
|
+
import { cancel as cancel3, log as log4, outro as outro2 } from "@clack/prompts";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import
|
|
7
|
+
import path8 from "path";
|
|
8
8
|
|
|
9
9
|
// src/commands/generate.ts
|
|
10
|
+
import { log as log2 } from "@clack/prompts";
|
|
11
|
+
import fs4 from "fs-extra";
|
|
12
|
+
import path4 from "path";
|
|
13
|
+
|
|
14
|
+
// src/commands/page.ts
|
|
10
15
|
import { log } from "@clack/prompts";
|
|
11
|
-
import
|
|
12
|
-
import
|
|
16
|
+
import fs3 from "fs-extra";
|
|
17
|
+
import path3 from "path";
|
|
13
18
|
|
|
14
19
|
// src/lib/naming.ts
|
|
15
20
|
function toPascalCase(value) {
|
|
@@ -26,6 +31,58 @@ function assertValidSliceName(name) {
|
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
// src/lib/page-prompts.ts
|
|
35
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
36
|
+
|
|
37
|
+
// src/lib/page-presets.ts
|
|
38
|
+
var PAGE_PRESETS = [
|
|
39
|
+
"auth",
|
|
40
|
+
"dashboard",
|
|
41
|
+
"crud",
|
|
42
|
+
"profile",
|
|
43
|
+
"settings",
|
|
44
|
+
"blank"
|
|
45
|
+
];
|
|
46
|
+
var DEFAULT_PAGE_PRESET = "blank";
|
|
47
|
+
var PAGE_PRESET_LABELS = {
|
|
48
|
+
auth: "auth \u2014 login/register/logout flow",
|
|
49
|
+
dashboard: "dashboard \u2014 layout with sidebar + analytics",
|
|
50
|
+
crud: "crud \u2014 list + create/edit/delete",
|
|
51
|
+
profile: "profile \u2014 user profile page",
|
|
52
|
+
settings: "settings \u2014 tabbed settings page",
|
|
53
|
+
blank: "blank \u2014 minimal page structure"
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/lib/page-prompts.ts
|
|
57
|
+
function exitOnCancel(value) {
|
|
58
|
+
if (isCancel(value)) {
|
|
59
|
+
cancel("Cancelled");
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
async function promptPagePreset(options) {
|
|
65
|
+
if (options.preset) {
|
|
66
|
+
if (!PAGE_PRESETS.includes(options.preset)) {
|
|
67
|
+
throw new Error(`Unknown preset "${options.preset}". Use: ${PAGE_PRESETS.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
return options.preset;
|
|
70
|
+
}
|
|
71
|
+
if (options.yes) {
|
|
72
|
+
return DEFAULT_PAGE_PRESET;
|
|
73
|
+
}
|
|
74
|
+
return exitOnCancel(
|
|
75
|
+
await select({
|
|
76
|
+
message: "What page template do you want?",
|
|
77
|
+
options: PAGE_PRESETS.map((preset) => ({
|
|
78
|
+
value: preset,
|
|
79
|
+
label: PAGE_PRESET_LABELS[preset]
|
|
80
|
+
})),
|
|
81
|
+
initialValue: "blank"
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
29
86
|
// src/lib/paths.ts
|
|
30
87
|
import path from "path";
|
|
31
88
|
import { fileURLToPath } from "url";
|
|
@@ -66,24 +123,9 @@ function resolveAppTemplateDir() {
|
|
|
66
123
|
return appTemplate;
|
|
67
124
|
}
|
|
68
125
|
|
|
69
|
-
// src/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
feature: "features",
|
|
73
|
-
view: "views",
|
|
74
|
-
widget: "widgets",
|
|
75
|
-
entity: "entities"
|
|
76
|
-
};
|
|
77
|
-
function isSliceType(value) {
|
|
78
|
-
return SLICE_TYPES.includes(value);
|
|
79
|
-
}
|
|
80
|
-
function assertNextProject(cwd) {
|
|
81
|
-
const packageJson = path2.join(cwd, "package.json");
|
|
82
|
-
const srcDir = path2.join(cwd, "src");
|
|
83
|
-
if (!fs2.existsSync(packageJson) || !fs2.existsSync(srcDir)) {
|
|
84
|
-
throw new Error("Run this command from the root of a Next Architecture project.");
|
|
85
|
-
}
|
|
86
|
-
}
|
|
126
|
+
// src/lib/template.ts
|
|
127
|
+
import fs2 from "fs-extra";
|
|
128
|
+
import path2 from "path";
|
|
87
129
|
async function renderTemplateDir(templateDir, targetDir, replacements) {
|
|
88
130
|
const created = [];
|
|
89
131
|
if (!await fs2.pathExists(templateDir)) {
|
|
@@ -108,44 +150,168 @@ async function renderTemplateDir(templateDir, targetDir, replacements) {
|
|
|
108
150
|
content = content.replaceAll(from, to);
|
|
109
151
|
}
|
|
110
152
|
await fs2.writeFile(targetPath, content);
|
|
111
|
-
created.push(
|
|
153
|
+
created.push(targetPath);
|
|
112
154
|
}
|
|
113
155
|
return created;
|
|
114
156
|
}
|
|
115
|
-
async function
|
|
116
|
-
const
|
|
157
|
+
async function copyTemplateTree(sourceDir, targetDir) {
|
|
158
|
+
const created = [];
|
|
159
|
+
if (!await fs2.pathExists(sourceDir)) {
|
|
160
|
+
return created;
|
|
161
|
+
}
|
|
162
|
+
const entries = await fs2.readdir(sourceDir, { withFileTypes: true });
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const sourcePath = path2.join(sourceDir, entry.name);
|
|
165
|
+
const targetPath = path2.join(targetDir, entry.name);
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
await fs2.ensureDir(targetPath);
|
|
168
|
+
created.push(...await copyTemplateTree(sourcePath, targetPath));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
await fs2.ensureDir(path2.dirname(targetPath));
|
|
172
|
+
await fs2.copy(sourcePath, targetPath);
|
|
173
|
+
created.push(targetPath);
|
|
174
|
+
}
|
|
175
|
+
return created;
|
|
176
|
+
}
|
|
177
|
+
function buildReplacements(name, pascalName, kebabName) {
|
|
178
|
+
return {
|
|
179
|
+
"{{Name}}": pascalName,
|
|
180
|
+
"{{name}}": kebabName,
|
|
181
|
+
"{{NAME}}": name.toUpperCase()
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/commands/page.ts
|
|
186
|
+
function assertNextProject(cwd) {
|
|
187
|
+
const packageJson = path3.join(cwd, "package.json");
|
|
188
|
+
const srcDir = path3.join(cwd, "src");
|
|
189
|
+
if (!fs3.existsSync(packageJson) || !fs3.existsSync(srcDir)) {
|
|
190
|
+
throw new Error("Run this command from the root of a Next Architecture project.");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function pathExistsAny(paths) {
|
|
194
|
+
for (const candidate of paths) {
|
|
195
|
+
if (await fs3.pathExists(candidate)) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
async function pageCommand(name, projectRoot = process.cwd(), options = {}) {
|
|
202
|
+
const root = path3.resolve(projectRoot);
|
|
117
203
|
assertNextProject(root);
|
|
118
204
|
assertValidSliceName(name);
|
|
205
|
+
const preset = await promptPagePreset({ yes: options.yes, preset: options.preset });
|
|
206
|
+
const pascalName = toPascalCase(name);
|
|
207
|
+
const kebabName = toKebabCase(name);
|
|
208
|
+
const templatesDir = resolveTemplatesDir();
|
|
209
|
+
const templateDir = path3.join(templatesDir, "pages", preset);
|
|
210
|
+
if (!await fs3.pathExists(templateDir)) {
|
|
211
|
+
throw new Error(`Page preset "${preset}" template not found.`);
|
|
212
|
+
}
|
|
213
|
+
const targetDir = path3.join(root, "src");
|
|
214
|
+
const replacements = buildReplacements(name, pascalName, kebabName);
|
|
215
|
+
const conflictPaths = [
|
|
216
|
+
path3.join(targetDir, "app", kebabName),
|
|
217
|
+
path3.join(targetDir, "app", `(${kebabName})`),
|
|
218
|
+
path3.join(targetDir, "views", kebabName),
|
|
219
|
+
path3.join(targetDir, "features", kebabName),
|
|
220
|
+
path3.join(targetDir, "entities", kebabName)
|
|
221
|
+
];
|
|
222
|
+
if (!options.force && await pathExistsAny(conflictPaths)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Page "${kebabName}" already exists. Use --force to overwrite conflicting paths.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const previousCwd = process.cwd();
|
|
228
|
+
process.chdir(root);
|
|
229
|
+
try {
|
|
230
|
+
if (options.force) {
|
|
231
|
+
for (const conflictPath of conflictPaths) {
|
|
232
|
+
if (await fs3.pathExists(conflictPath)) {
|
|
233
|
+
await fs3.remove(conflictPath);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const created = await renderTemplateDir(templateDir, root, replacements);
|
|
238
|
+
const relativeFiles = created.map((file) => path3.relative(root, file));
|
|
239
|
+
log.success(`Created ${preset} page "${kebabName}"`);
|
|
240
|
+
for (const file of relativeFiles) {
|
|
241
|
+
log.info(` ${file}`);
|
|
242
|
+
}
|
|
243
|
+
if (preset === "blank") {
|
|
244
|
+
log.info(`Route: src/app/${kebabName}/page.tsx`);
|
|
245
|
+
}
|
|
246
|
+
if (preset === "auth") {
|
|
247
|
+
log.info(`Routes: src/app/(${kebabName})/login and register`);
|
|
248
|
+
}
|
|
249
|
+
if (preset === "crud") {
|
|
250
|
+
log.info(`Routes: src/app/${kebabName}, ${kebabName}/[id], ${kebabName}/new`);
|
|
251
|
+
}
|
|
252
|
+
} finally {
|
|
253
|
+
process.chdir(previousCwd);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/commands/generate.ts
|
|
258
|
+
var SLICE_TYPES = ["feature", "view", "widget", "entity"];
|
|
259
|
+
var TARGET_DIRS = {
|
|
260
|
+
feature: "features",
|
|
261
|
+
view: "views",
|
|
262
|
+
widget: "widgets",
|
|
263
|
+
entity: "entities"
|
|
264
|
+
};
|
|
265
|
+
function isSliceType(value) {
|
|
266
|
+
return SLICE_TYPES.includes(value);
|
|
267
|
+
}
|
|
268
|
+
function assertNextProject2(cwd) {
|
|
269
|
+
const packageJson = path4.join(cwd, "package.json");
|
|
270
|
+
const srcDir = path4.join(cwd, "src");
|
|
271
|
+
if (!fs4.existsSync(packageJson) || !fs4.existsSync(srcDir)) {
|
|
272
|
+
throw new Error("Run this command from the root of a Next Architecture project.");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async function generateCommand(type, name, projectRoot = process.cwd(), options = {}) {
|
|
276
|
+
if (type === "page") {
|
|
277
|
+
await pageCommand(name, projectRoot, {
|
|
278
|
+
force: options.force,
|
|
279
|
+
yes: options.yes,
|
|
280
|
+
preset: options.preset
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const root = path4.resolve(projectRoot);
|
|
285
|
+
assertNextProject2(root);
|
|
286
|
+
assertValidSliceName(name);
|
|
119
287
|
if (!isSliceType(type)) {
|
|
120
|
-
throw new Error(`Unknown type "${type}". Use: ${SLICE_TYPES.join(", ")}`);
|
|
288
|
+
throw new Error(`Unknown type "${type}". Use: page, ${SLICE_TYPES.join(", ")}`);
|
|
121
289
|
}
|
|
122
290
|
const pascalName = toPascalCase(name);
|
|
123
291
|
const kebabName = toKebabCase(name);
|
|
124
292
|
const templatesDir = resolveTemplatesDir();
|
|
125
|
-
const templateDir =
|
|
126
|
-
const targetDir =
|
|
293
|
+
const templateDir = path4.join(templatesDir, type);
|
|
294
|
+
const targetDir = path4.join(root, "src", TARGET_DIRS[type], kebabName);
|
|
127
295
|
const previousCwd = process.cwd();
|
|
128
296
|
process.chdir(root);
|
|
129
297
|
try {
|
|
130
|
-
if (await
|
|
298
|
+
if (await fs4.pathExists(targetDir)) {
|
|
131
299
|
if (!options.force) {
|
|
132
300
|
throw new Error(
|
|
133
301
|
`"${type}" "${kebabName}" already exists at ${targetDir}. Use --force to overwrite.`
|
|
134
302
|
);
|
|
135
303
|
}
|
|
136
|
-
await
|
|
304
|
+
await fs4.remove(targetDir);
|
|
137
305
|
}
|
|
138
|
-
const replacements =
|
|
139
|
-
"{{Name}}": pascalName,
|
|
140
|
-
"{{name}}": kebabName
|
|
141
|
-
};
|
|
306
|
+
const replacements = buildReplacements(name, pascalName, kebabName);
|
|
142
307
|
const created = await renderTemplateDir(templateDir, targetDir, replacements);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
308
|
+
const relativeFiles = created.map((file) => path4.relative(root, file));
|
|
309
|
+
log2.success(`Created ${type} "${kebabName}" in ${root}`);
|
|
310
|
+
for (const file of relativeFiles) {
|
|
311
|
+
log2.info(` ${file}`);
|
|
146
312
|
}
|
|
147
313
|
if (type === "view") {
|
|
148
|
-
|
|
314
|
+
log2.info(`Add to a route: import { ${pascalName} } from '@/views/${kebabName}';`);
|
|
149
315
|
}
|
|
150
316
|
} finally {
|
|
151
317
|
process.chdir(previousCwd);
|
|
@@ -153,33 +319,324 @@ async function generateCommand(type, name, projectRoot = process.cwd(), options
|
|
|
153
319
|
}
|
|
154
320
|
|
|
155
321
|
// src/commands/init.ts
|
|
156
|
-
import { confirm, intro, log as
|
|
157
|
-
import
|
|
158
|
-
import
|
|
322
|
+
import { confirm as confirm2, intro, log as log3, outro } from "@clack/prompts";
|
|
323
|
+
import fs7 from "fs-extra";
|
|
324
|
+
import path7 from "path";
|
|
325
|
+
|
|
326
|
+
// src/lib/apply-packages.ts
|
|
327
|
+
import fs5 from "fs-extra";
|
|
328
|
+
import path5 from "path";
|
|
329
|
+
|
|
330
|
+
// src/lib/packages.ts
|
|
331
|
+
var DEFAULT_INIT_SELECTIONS = {
|
|
332
|
+
stateManager: "zustand",
|
|
333
|
+
formLibrary: "tanstack-form",
|
|
334
|
+
optionalPackages: ["tanstack-table"],
|
|
335
|
+
withExamples: true
|
|
336
|
+
};
|
|
337
|
+
var PACKAGE_VERSIONS = {
|
|
338
|
+
zustand: "^5.0.14",
|
|
339
|
+
"@reduxjs/toolkit": "^2.8.2",
|
|
340
|
+
"react-redux": "^9.2.0",
|
|
341
|
+
jotai: "^2.12.5",
|
|
342
|
+
"@tanstack/react-form": "^1.0.0",
|
|
343
|
+
zod: "^4.4.3",
|
|
344
|
+
"react-hook-form": "^7.56.4",
|
|
345
|
+
"@hookform/resolvers": "^5.0.1",
|
|
346
|
+
"@tanstack/react-query": "^5.101.2",
|
|
347
|
+
"@tanstack/react-query-devtools": "^5.101.2",
|
|
348
|
+
"@tanstack/react-table": "^8.21.3",
|
|
349
|
+
motion: "^12.19.1",
|
|
350
|
+
nuqs: "^2.4.3",
|
|
351
|
+
"@trpc/client": "^11.1.2",
|
|
352
|
+
"@trpc/server": "^11.1.2",
|
|
353
|
+
"@trpc/react-query": "^11.1.2",
|
|
354
|
+
"better-auth": "^1.2.8",
|
|
355
|
+
uploadthing: "^7.7.2",
|
|
356
|
+
"@uploadthing/react": "^7.3.1",
|
|
357
|
+
sonner: "^2.0.3",
|
|
358
|
+
"next-intl": "^4.1.0",
|
|
359
|
+
"@sentry/nextjs": "^10.58.0"
|
|
360
|
+
};
|
|
361
|
+
function resolveDependencies(selections) {
|
|
362
|
+
const dependencies = {};
|
|
363
|
+
const devDependencies = {};
|
|
364
|
+
dependencies["@tanstack/react-query"] = PACKAGE_VERSIONS["@tanstack/react-query"];
|
|
365
|
+
devDependencies["@tanstack/react-query-devtools"] = PACKAGE_VERSIONS["@tanstack/react-query-devtools"];
|
|
366
|
+
dependencies.zod = PACKAGE_VERSIONS.zod;
|
|
367
|
+
switch (selections.stateManager) {
|
|
368
|
+
case "zustand":
|
|
369
|
+
dependencies.zustand = PACKAGE_VERSIONS.zustand;
|
|
370
|
+
break;
|
|
371
|
+
case "redux":
|
|
372
|
+
dependencies["@reduxjs/toolkit"] = PACKAGE_VERSIONS["@reduxjs/toolkit"];
|
|
373
|
+
dependencies["react-redux"] = PACKAGE_VERSIONS["react-redux"];
|
|
374
|
+
break;
|
|
375
|
+
case "jotai":
|
|
376
|
+
dependencies.jotai = PACKAGE_VERSIONS.jotai;
|
|
377
|
+
break;
|
|
378
|
+
case "none":
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
switch (selections.formLibrary) {
|
|
382
|
+
case "tanstack-form":
|
|
383
|
+
dependencies["@tanstack/react-form"] = PACKAGE_VERSIONS["@tanstack/react-form"];
|
|
384
|
+
break;
|
|
385
|
+
case "react-hook-form":
|
|
386
|
+
dependencies["react-hook-form"] = PACKAGE_VERSIONS["react-hook-form"];
|
|
387
|
+
dependencies["@hookform/resolvers"] = PACKAGE_VERSIONS["@hookform/resolvers"];
|
|
388
|
+
break;
|
|
389
|
+
case "none":
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
for (const pkg of selections.optionalPackages) {
|
|
393
|
+
switch (pkg) {
|
|
394
|
+
case "tanstack-table":
|
|
395
|
+
dependencies["@tanstack/react-table"] = PACKAGE_VERSIONS["@tanstack/react-table"];
|
|
396
|
+
break;
|
|
397
|
+
case "motion":
|
|
398
|
+
dependencies.motion = PACKAGE_VERSIONS.motion;
|
|
399
|
+
break;
|
|
400
|
+
case "nuqs":
|
|
401
|
+
dependencies.nuqs = PACKAGE_VERSIONS.nuqs;
|
|
402
|
+
break;
|
|
403
|
+
case "trpc":
|
|
404
|
+
dependencies["@trpc/client"] = PACKAGE_VERSIONS["@trpc/client"];
|
|
405
|
+
dependencies["@trpc/server"] = PACKAGE_VERSIONS["@trpc/server"];
|
|
406
|
+
dependencies["@trpc/react-query"] = PACKAGE_VERSIONS["@trpc/react-query"];
|
|
407
|
+
break;
|
|
408
|
+
case "better-auth":
|
|
409
|
+
dependencies["better-auth"] = PACKAGE_VERSIONS["better-auth"];
|
|
410
|
+
break;
|
|
411
|
+
case "uploadthing":
|
|
412
|
+
dependencies.uploadthing = PACKAGE_VERSIONS.uploadthing;
|
|
413
|
+
dependencies["@uploadthing/react"] = PACKAGE_VERSIONS["@uploadthing/react"];
|
|
414
|
+
break;
|
|
415
|
+
case "sonner":
|
|
416
|
+
dependencies.sonner = PACKAGE_VERSIONS.sonner;
|
|
417
|
+
break;
|
|
418
|
+
case "next-intl":
|
|
419
|
+
dependencies["next-intl"] = PACKAGE_VERSIONS["next-intl"];
|
|
420
|
+
break;
|
|
421
|
+
case "sentry":
|
|
422
|
+
dependencies["@sentry/nextjs"] = PACKAGE_VERSIONS["@sentry/nextjs"];
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return { dependencies, devDependencies };
|
|
427
|
+
}
|
|
428
|
+
function getPackageTemplates(selections) {
|
|
429
|
+
const templates = /* @__PURE__ */ new Set(["tanstack-query", "env"]);
|
|
430
|
+
switch (selections.stateManager) {
|
|
431
|
+
case "zustand":
|
|
432
|
+
templates.add("zustand");
|
|
433
|
+
break;
|
|
434
|
+
case "redux":
|
|
435
|
+
templates.add("redux");
|
|
436
|
+
break;
|
|
437
|
+
case "jotai":
|
|
438
|
+
templates.add("jotai");
|
|
439
|
+
break;
|
|
440
|
+
case "none":
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
switch (selections.formLibrary) {
|
|
444
|
+
case "tanstack-form":
|
|
445
|
+
templates.add("tanstack-form");
|
|
446
|
+
break;
|
|
447
|
+
case "react-hook-form":
|
|
448
|
+
templates.add("react-hook-form");
|
|
449
|
+
break;
|
|
450
|
+
case "none":
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
for (const pkg of selections.optionalPackages) {
|
|
454
|
+
templates.add(pkg);
|
|
455
|
+
}
|
|
456
|
+
if (selections.optionalPackages.includes("sonner")) {
|
|
457
|
+
templates.add("sonner-provider");
|
|
458
|
+
}
|
|
459
|
+
return [...templates];
|
|
460
|
+
}
|
|
461
|
+
function formatSelectionsSummary(selections) {
|
|
462
|
+
const lines = [];
|
|
463
|
+
const stateLabels = {
|
|
464
|
+
zustand: "Zustand",
|
|
465
|
+
redux: "Redux Toolkit",
|
|
466
|
+
jotai: "Jotai",
|
|
467
|
+
none: "None"
|
|
468
|
+
};
|
|
469
|
+
const formLabels = {
|
|
470
|
+
"tanstack-form": "TanStack Form + zod",
|
|
471
|
+
"react-hook-form": "React Hook Form + zod",
|
|
472
|
+
none: "None"
|
|
473
|
+
};
|
|
474
|
+
lines.push(`State: ${stateLabels[selections.stateManager]}`);
|
|
475
|
+
lines.push(`Forms: ${formLabels[selections.formLibrary]}`);
|
|
476
|
+
lines.push("Always: TanStack Query + Devtools");
|
|
477
|
+
const optionalLabels = {
|
|
478
|
+
"tanstack-table": "TanStack Table",
|
|
479
|
+
motion: "Motion",
|
|
480
|
+
nuqs: "nuqs",
|
|
481
|
+
trpc: "tRPC",
|
|
482
|
+
"better-auth": "Better Auth",
|
|
483
|
+
uploadthing: "Uploadthing",
|
|
484
|
+
sonner: "Sonner",
|
|
485
|
+
"next-intl": "next-intl",
|
|
486
|
+
"sentry": "Sentry"
|
|
487
|
+
};
|
|
488
|
+
if (selections.optionalPackages.length > 0) {
|
|
489
|
+
lines.push(
|
|
490
|
+
`Optional: ${selections.optionalPackages.map((p) => optionalLabels[p]).join(", ")}`
|
|
491
|
+
);
|
|
492
|
+
} else {
|
|
493
|
+
lines.push("Optional: none");
|
|
494
|
+
}
|
|
495
|
+
lines.push(`Examples: ${selections.withExamples ? "yes" : "no"}`);
|
|
496
|
+
return lines;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/lib/apply-packages.ts
|
|
500
|
+
async function mergePackageJson(targetDir, selections) {
|
|
501
|
+
const packageJsonPath = path5.join(targetDir, "package.json");
|
|
502
|
+
const pkg = JSON.parse(await fs5.readFile(packageJsonPath, "utf8"));
|
|
503
|
+
const { dependencies, devDependencies } = resolveDependencies(selections);
|
|
504
|
+
pkg.dependencies = { ...pkg.dependencies, ...dependencies };
|
|
505
|
+
pkg.devDependencies = { ...pkg.devDependencies, ...devDependencies };
|
|
506
|
+
await fs5.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
|
|
507
|
+
`);
|
|
508
|
+
}
|
|
509
|
+
async function applyPackageSelections(targetDir, selections) {
|
|
510
|
+
const packagesDir = path5.join(resolveTemplatesDir(), "packages");
|
|
511
|
+
const created = [];
|
|
512
|
+
await mergePackageJson(targetDir, selections);
|
|
513
|
+
for (const templateId of getPackageTemplates(selections)) {
|
|
514
|
+
const templateRoot = path5.join(packagesDir, templateId);
|
|
515
|
+
const coreSrc = path5.join(templateRoot, "core", "src");
|
|
516
|
+
const examplesSrc = path5.join(templateRoot, "examples", "src");
|
|
517
|
+
if (await fs5.pathExists(coreSrc)) {
|
|
518
|
+
const files = await copyTemplateTree(coreSrc, path5.join(targetDir, "src"));
|
|
519
|
+
created.push(...files);
|
|
520
|
+
}
|
|
521
|
+
if (selections.withExamples && await fs5.pathExists(examplesSrc)) {
|
|
522
|
+
const files = await copyTemplateTree(examplesSrc, path5.join(targetDir, "src"));
|
|
523
|
+
created.push(...files);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return created;
|
|
527
|
+
}
|
|
159
528
|
|
|
160
529
|
// src/lib/copy.ts
|
|
161
|
-
import
|
|
162
|
-
import
|
|
530
|
+
import fs6 from "fs-extra";
|
|
531
|
+
import path6 from "path";
|
|
163
532
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".turbo", "dist"]);
|
|
164
533
|
async function copyProjectTemplate(sourceDir, targetDir) {
|
|
165
|
-
await
|
|
534
|
+
await fs6.copy(sourceDir, targetDir, {
|
|
166
535
|
filter(src) {
|
|
167
|
-
const relative =
|
|
536
|
+
const relative = path6.relative(sourceDir, src);
|
|
168
537
|
if (!relative) return true;
|
|
169
|
-
return !relative.split(
|
|
538
|
+
return !relative.split(path6.sep).some((part) => EXCLUDED_DIRS.has(part));
|
|
170
539
|
}
|
|
171
540
|
});
|
|
172
541
|
}
|
|
173
542
|
|
|
543
|
+
// src/lib/init-prompts.ts
|
|
544
|
+
import { cancel as cancel2, confirm, isCancel as isCancel2, multiselect, select as select2 } from "@clack/prompts";
|
|
545
|
+
function exitOnCancel2(value) {
|
|
546
|
+
if (isCancel2(value)) {
|
|
547
|
+
cancel2("Cancelled");
|
|
548
|
+
process.exit(0);
|
|
549
|
+
}
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
async function promptInitSelections(options = {}) {
|
|
553
|
+
if (options.yes) {
|
|
554
|
+
return {
|
|
555
|
+
...DEFAULT_INIT_SELECTIONS,
|
|
556
|
+
withExamples: options.noExamples ? false : DEFAULT_INIT_SELECTIONS.withExamples
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const stateManager = exitOnCancel2(
|
|
560
|
+
await select2({
|
|
561
|
+
message: "Which state manager do you want?",
|
|
562
|
+
options: [
|
|
563
|
+
{ value: "zustand", label: "Zustand (recommended)" },
|
|
564
|
+
{ value: "redux", label: "Redux Toolkit" },
|
|
565
|
+
{ value: "jotai", label: "Jotai" },
|
|
566
|
+
{ value: "none", label: "None" }
|
|
567
|
+
],
|
|
568
|
+
initialValue: "zustand"
|
|
569
|
+
})
|
|
570
|
+
);
|
|
571
|
+
const formLibrary = exitOnCancel2(
|
|
572
|
+
await select2({
|
|
573
|
+
message: "Which form library do you want?",
|
|
574
|
+
options: [
|
|
575
|
+
{ value: "tanstack-form", label: "TanStack Form (recommended)" },
|
|
576
|
+
{ value: "react-hook-form", label: "React Hook Form" },
|
|
577
|
+
{ value: "none", label: "None" }
|
|
578
|
+
],
|
|
579
|
+
initialValue: "tanstack-form"
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
const optionalPackages = exitOnCancel2(
|
|
583
|
+
await multiselect({
|
|
584
|
+
message: "Select additional packages (multi-select)",
|
|
585
|
+
options: [
|
|
586
|
+
{ value: "tanstack-table", label: "TanStack Table (headless tables)" },
|
|
587
|
+
{ value: "motion", label: "Motion / Framer Motion (animations)" },
|
|
588
|
+
{ value: "nuqs", label: "nuqs (URL state management)" },
|
|
589
|
+
{ value: "trpc", label: "tRPC (end-to-end type safety, if using separate backend)" },
|
|
590
|
+
{ value: "better-auth", label: "Better Auth (alternative to NextAuth)" },
|
|
591
|
+
{ value: "uploadthing", label: "Uploadthing (file uploads)" },
|
|
592
|
+
{ value: "sonner", label: "Sonner (toast notifications)" },
|
|
593
|
+
{ value: "next-intl", label: "next-intl (i18n)" },
|
|
594
|
+
{ value: "sentry", label: "Sentry (error tracking)" }
|
|
595
|
+
],
|
|
596
|
+
initialValues: ["tanstack-table"],
|
|
597
|
+
required: false
|
|
598
|
+
})
|
|
599
|
+
);
|
|
600
|
+
let withExamples = true;
|
|
601
|
+
if (!options.noExamples) {
|
|
602
|
+
withExamples = exitOnCancel2(
|
|
603
|
+
await confirm({
|
|
604
|
+
message: "Generate example files with comments (Russian)?",
|
|
605
|
+
initialValue: true
|
|
606
|
+
})
|
|
607
|
+
);
|
|
608
|
+
} else {
|
|
609
|
+
withExamples = false;
|
|
610
|
+
}
|
|
611
|
+
const selections = {
|
|
612
|
+
stateManager,
|
|
613
|
+
formLibrary,
|
|
614
|
+
optionalPackages,
|
|
615
|
+
withExamples
|
|
616
|
+
};
|
|
617
|
+
const proceed = exitOnCancel2(
|
|
618
|
+
await confirm({
|
|
619
|
+
message: `Proceed with this setup?
|
|
620
|
+
${formatSelectionsSummary(selections).map((l) => ` \u2022 ${l}`).join("\n")}`,
|
|
621
|
+
initialValue: true
|
|
622
|
+
})
|
|
623
|
+
);
|
|
624
|
+
if (!proceed) {
|
|
625
|
+
cancel2("Cancelled");
|
|
626
|
+
process.exit(0);
|
|
627
|
+
}
|
|
628
|
+
return selections;
|
|
629
|
+
}
|
|
630
|
+
|
|
174
631
|
// src/commands/init.ts
|
|
175
632
|
async function resolveEslintPluginSource() {
|
|
176
633
|
const packageRoot = getPackageRoot();
|
|
177
634
|
const candidates = [
|
|
178
|
-
|
|
179
|
-
|
|
635
|
+
path7.join(packageRoot, "vendor", "eslint-plugin-next-arch"),
|
|
636
|
+
path7.resolve(packageRoot, "..", "..", "packages", "eslint-plugin-next-arch")
|
|
180
637
|
];
|
|
181
638
|
for (const candidate of candidates) {
|
|
182
|
-
if (await
|
|
639
|
+
if (await fs7.pathExists(path7.join(candidate, "dist", "index.js"))) {
|
|
183
640
|
return candidate;
|
|
184
641
|
}
|
|
185
642
|
}
|
|
@@ -189,29 +646,33 @@ async function resolveEslintPluginSource() {
|
|
|
189
646
|
}
|
|
190
647
|
async function bundleEslintPlugin(targetDir) {
|
|
191
648
|
const pluginSource = await resolveEslintPluginSource();
|
|
192
|
-
const pluginTarget =
|
|
193
|
-
await
|
|
194
|
-
await
|
|
195
|
-
await
|
|
649
|
+
const pluginTarget = path7.join(targetDir, "vendor", "eslint-plugin-next-arch");
|
|
650
|
+
await fs7.ensureDir(pluginTarget);
|
|
651
|
+
await fs7.copy(path7.join(pluginSource, "dist"), path7.join(pluginTarget, "dist"));
|
|
652
|
+
await fs7.copy(path7.join(pluginSource, "package.json"), path7.join(pluginTarget, "package.json"));
|
|
196
653
|
}
|
|
197
654
|
async function patchPackageJson(targetDir, projectName) {
|
|
198
|
-
const packageJsonPath =
|
|
199
|
-
const pkg = JSON.parse(await
|
|
655
|
+
const packageJsonPath = path7.join(targetDir, "package.json");
|
|
656
|
+
const pkg = JSON.parse(await fs7.readFile(packageJsonPath, "utf8"));
|
|
200
657
|
pkg.name = projectName;
|
|
201
658
|
delete pkg.scripts?.arch;
|
|
202
659
|
if (pkg.devDependencies) {
|
|
203
660
|
pkg.devDependencies["eslint-plugin-next-arch"] = "file:./vendor/eslint-plugin-next-arch";
|
|
204
661
|
}
|
|
205
|
-
await
|
|
662
|
+
await fs7.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
|
|
206
663
|
`);
|
|
207
664
|
}
|
|
208
665
|
async function initCommand(projectName, options = {}) {
|
|
209
|
-
const baseDir =
|
|
666
|
+
const baseDir = path7.resolve(options.cwd ?? process.cwd());
|
|
210
667
|
intro("Creating new Next Architecture project...");
|
|
211
|
-
const
|
|
668
|
+
const selections = await promptInitSelections({
|
|
669
|
+
yes: options.yes,
|
|
670
|
+
noExamples: options.noExamples
|
|
671
|
+
});
|
|
672
|
+
const targetDir = path7.join(baseDir, projectName);
|
|
212
673
|
const templateDir = resolveAppTemplateDir();
|
|
213
|
-
if (await
|
|
214
|
-
const shouldContinue = await
|
|
674
|
+
if (await fs7.pathExists(targetDir)) {
|
|
675
|
+
const shouldContinue = await confirm2({
|
|
215
676
|
message: "Directory already exists. Continue and merge files?"
|
|
216
677
|
});
|
|
217
678
|
if (!shouldContinue) {
|
|
@@ -219,41 +680,76 @@ async function initCommand(projectName, options = {}) {
|
|
|
219
680
|
return;
|
|
220
681
|
}
|
|
221
682
|
}
|
|
222
|
-
|
|
683
|
+
log3.info("Package setup:");
|
|
684
|
+
for (const line of formatSelectionsSummary(selections)) {
|
|
685
|
+
log3.info(` ${line}`);
|
|
686
|
+
}
|
|
687
|
+
log3.info(`Copying template from ${path7.basename(templateDir)}...`);
|
|
223
688
|
await copyProjectTemplate(templateDir, targetDir);
|
|
224
689
|
await bundleEslintPlugin(targetDir);
|
|
225
690
|
await patchPackageJson(targetDir, projectName);
|
|
226
|
-
await
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
691
|
+
await applyPackageSelections(targetDir, selections);
|
|
692
|
+
log3.success(`Project "${projectName}" created`);
|
|
693
|
+
log3.info(` cd ${projectName}`);
|
|
694
|
+
log3.info(" npm install");
|
|
695
|
+
log3.info(" npm run dev");
|
|
696
|
+
if (selections.withExamples) {
|
|
697
|
+
log3.info(" See src/features/_examples/ for commented package examples");
|
|
698
|
+
}
|
|
231
699
|
outro("Done!");
|
|
232
700
|
}
|
|
233
701
|
|
|
234
702
|
// src/index.ts
|
|
235
703
|
console.log(chalk.blue("Next Architecture CLI"));
|
|
236
|
-
program.name("next-arch").description("CLI for Next.js Feature-Sliced Architecture").version("0.
|
|
237
|
-
program.command("init <projectName>").description("Create a new project with Next Architecture").option("-C, --cwd <path>", "directory where the project folder will be created").
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
704
|
+
program.name("next-arch").description("CLI for Next.js Feature-Sliced Architecture").version("0.2.1");
|
|
705
|
+
program.command("init <projectName>").description("Create a new project with Next Architecture").option("-C, --cwd <path>", "directory where the project folder will be created").option("-y, --yes", "use default package selections without prompts").option("--no-examples", "skip generating example files").action(
|
|
706
|
+
async (projectName, options) => {
|
|
707
|
+
try {
|
|
708
|
+
await initCommand(projectName, {
|
|
709
|
+
cwd: options.cwd,
|
|
710
|
+
yes: options.yes,
|
|
711
|
+
noExamples: options.examples === false
|
|
712
|
+
});
|
|
713
|
+
} catch (error) {
|
|
714
|
+
cancel3(error instanceof Error ? error.message : "Init failed");
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
243
717
|
}
|
|
244
|
-
|
|
245
|
-
program.command("
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
718
|
+
);
|
|
719
|
+
program.command("page <name>").description("Generate a full FSD page (view + feature + routes)").option("-C, --cwd <path>", "path to Next.js project root (default: current directory)").option("-f, --force", "overwrite existing page paths").option("-y, --yes", "use blank preset without prompts").option("--preset <preset>", "page preset: auth, dashboard, crud, profile, settings, blank").action(
|
|
720
|
+
async (name, options) => {
|
|
721
|
+
try {
|
|
722
|
+
const projectRoot = options.cwd ? path8.resolve(options.cwd) : process.cwd();
|
|
723
|
+
await pageCommand(name, projectRoot, {
|
|
724
|
+
force: options.force,
|
|
725
|
+
yes: options.yes,
|
|
726
|
+
preset: options.preset
|
|
727
|
+
});
|
|
728
|
+
outro2("Done!");
|
|
729
|
+
} catch (error) {
|
|
730
|
+
cancel3(error instanceof Error ? error.message : "Page generation failed");
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
253
733
|
}
|
|
254
|
-
|
|
734
|
+
);
|
|
735
|
+
program.command("generate <type> <name>").alias("g").description("Generate page, feature, widget, entity, or view").option("-C, --cwd <path>", "path to Next.js project root (default: current directory)").option("-f, --force", "overwrite existing slice").option("-y, --yes", "skip interactive page preset selection").option("--preset <preset>", "page preset when type is page").action(
|
|
736
|
+
async (type, name, options) => {
|
|
737
|
+
try {
|
|
738
|
+
const projectRoot = options.cwd ? path8.resolve(options.cwd) : process.cwd();
|
|
739
|
+
await generateCommand(type, name, projectRoot, {
|
|
740
|
+
force: options.force,
|
|
741
|
+
yes: options.yes,
|
|
742
|
+
preset: options.preset
|
|
743
|
+
});
|
|
744
|
+
outro2("Done!");
|
|
745
|
+
} catch (error) {
|
|
746
|
+
cancel3(error instanceof Error ? error.message : "Generate failed");
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
);
|
|
255
751
|
program.parseAsync(process.argv).catch((error) => {
|
|
256
|
-
|
|
752
|
+
log4.error(error instanceof Error ? error.message : "Unexpected error");
|
|
257
753
|
process.exit(1);
|
|
258
754
|
});
|
|
259
755
|
//# sourceMappingURL=index.js.map
|