@yousxlfs/next-arch 0.1.0 → 0.2.0
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 +586 -85
- 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,17 +31,73 @@ 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";
|
|
32
89
|
import fs from "fs-extra";
|
|
90
|
+
var PACKAGE_NAMES = /* @__PURE__ */ new Set(["next-arch", "@yousxlfs/next-arch"]);
|
|
91
|
+
function isNextArchPackage(pkg) {
|
|
92
|
+
return typeof pkg.name === "string" && PACKAGE_NAMES.has(pkg.name);
|
|
93
|
+
}
|
|
33
94
|
function findPackageRoot(startDir) {
|
|
34
95
|
let current = startDir;
|
|
35
96
|
while (current !== path.dirname(current)) {
|
|
36
97
|
const packageJsonPath = path.join(current, "package.json");
|
|
37
98
|
if (fs.existsSync(packageJsonPath)) {
|
|
38
99
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
39
|
-
if (pkg
|
|
100
|
+
if (isNextArchPackage(pkg)) {
|
|
40
101
|
return current;
|
|
41
102
|
}
|
|
42
103
|
}
|
|
@@ -62,24 +123,9 @@ function resolveAppTemplateDir() {
|
|
|
62
123
|
return appTemplate;
|
|
63
124
|
}
|
|
64
125
|
|
|
65
|
-
// src/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
feature: "features",
|
|
69
|
-
view: "views",
|
|
70
|
-
widget: "widgets",
|
|
71
|
-
entity: "entities"
|
|
72
|
-
};
|
|
73
|
-
function isSliceType(value) {
|
|
74
|
-
return SLICE_TYPES.includes(value);
|
|
75
|
-
}
|
|
76
|
-
function assertNextProject(cwd) {
|
|
77
|
-
const packageJson = path2.join(cwd, "package.json");
|
|
78
|
-
const srcDir = path2.join(cwd, "src");
|
|
79
|
-
if (!fs2.existsSync(packageJson) || !fs2.existsSync(srcDir)) {
|
|
80
|
-
throw new Error("Run this command from the root of a Next Architecture project.");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
126
|
+
// src/lib/template.ts
|
|
127
|
+
import fs2 from "fs-extra";
|
|
128
|
+
import path2 from "path";
|
|
83
129
|
async function renderTemplateDir(templateDir, targetDir, replacements) {
|
|
84
130
|
const created = [];
|
|
85
131
|
if (!await fs2.pathExists(templateDir)) {
|
|
@@ -104,44 +150,168 @@ async function renderTemplateDir(templateDir, targetDir, replacements) {
|
|
|
104
150
|
content = content.replaceAll(from, to);
|
|
105
151
|
}
|
|
106
152
|
await fs2.writeFile(targetPath, content);
|
|
107
|
-
created.push(
|
|
153
|
+
created.push(targetPath);
|
|
108
154
|
}
|
|
109
155
|
return created;
|
|
110
156
|
}
|
|
111
|
-
async function
|
|
112
|
-
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);
|
|
113
203
|
assertNextProject(root);
|
|
114
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);
|
|
115
287
|
if (!isSliceType(type)) {
|
|
116
|
-
throw new Error(`Unknown type "${type}". Use: ${SLICE_TYPES.join(", ")}`);
|
|
288
|
+
throw new Error(`Unknown type "${type}". Use: page, ${SLICE_TYPES.join(", ")}`);
|
|
117
289
|
}
|
|
118
290
|
const pascalName = toPascalCase(name);
|
|
119
291
|
const kebabName = toKebabCase(name);
|
|
120
292
|
const templatesDir = resolveTemplatesDir();
|
|
121
|
-
const templateDir =
|
|
122
|
-
const targetDir =
|
|
293
|
+
const templateDir = path4.join(templatesDir, type);
|
|
294
|
+
const targetDir = path4.join(root, "src", TARGET_DIRS[type], kebabName);
|
|
123
295
|
const previousCwd = process.cwd();
|
|
124
296
|
process.chdir(root);
|
|
125
297
|
try {
|
|
126
|
-
if (await
|
|
298
|
+
if (await fs4.pathExists(targetDir)) {
|
|
127
299
|
if (!options.force) {
|
|
128
300
|
throw new Error(
|
|
129
301
|
`"${type}" "${kebabName}" already exists at ${targetDir}. Use --force to overwrite.`
|
|
130
302
|
);
|
|
131
303
|
}
|
|
132
|
-
await
|
|
304
|
+
await fs4.remove(targetDir);
|
|
133
305
|
}
|
|
134
|
-
const replacements =
|
|
135
|
-
"{{Name}}": pascalName,
|
|
136
|
-
"{{name}}": kebabName
|
|
137
|
-
};
|
|
306
|
+
const replacements = buildReplacements(name, pascalName, kebabName);
|
|
138
307
|
const created = await renderTemplateDir(templateDir, targetDir, replacements);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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}`);
|
|
142
312
|
}
|
|
143
313
|
if (type === "view") {
|
|
144
|
-
|
|
314
|
+
log2.info(`Add to a route: import { ${pascalName} } from '@/views/${kebabName}';`);
|
|
145
315
|
}
|
|
146
316
|
} finally {
|
|
147
317
|
process.chdir(previousCwd);
|
|
@@ -149,33 +319,324 @@ async function generateCommand(type, name, projectRoot = process.cwd(), options
|
|
|
149
319
|
}
|
|
150
320
|
|
|
151
321
|
// src/commands/init.ts
|
|
152
|
-
import { confirm, intro, log as
|
|
153
|
-
import
|
|
154
|
-
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": "^9.22.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
|
+
}
|
|
155
528
|
|
|
156
529
|
// src/lib/copy.ts
|
|
157
|
-
import
|
|
158
|
-
import
|
|
530
|
+
import fs6 from "fs-extra";
|
|
531
|
+
import path6 from "path";
|
|
159
532
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".turbo", "dist"]);
|
|
160
533
|
async function copyProjectTemplate(sourceDir, targetDir) {
|
|
161
|
-
await
|
|
534
|
+
await fs6.copy(sourceDir, targetDir, {
|
|
162
535
|
filter(src) {
|
|
163
|
-
const relative =
|
|
536
|
+
const relative = path6.relative(sourceDir, src);
|
|
164
537
|
if (!relative) return true;
|
|
165
|
-
return !relative.split(
|
|
538
|
+
return !relative.split(path6.sep).some((part) => EXCLUDED_DIRS.has(part));
|
|
166
539
|
}
|
|
167
540
|
});
|
|
168
541
|
}
|
|
169
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
|
+
|
|
170
631
|
// src/commands/init.ts
|
|
171
632
|
async function resolveEslintPluginSource() {
|
|
172
633
|
const packageRoot = getPackageRoot();
|
|
173
634
|
const candidates = [
|
|
174
|
-
|
|
175
|
-
|
|
635
|
+
path7.join(packageRoot, "vendor", "eslint-plugin-next-arch"),
|
|
636
|
+
path7.resolve(packageRoot, "..", "..", "packages", "eslint-plugin-next-arch")
|
|
176
637
|
];
|
|
177
638
|
for (const candidate of candidates) {
|
|
178
|
-
if (await
|
|
639
|
+
if (await fs7.pathExists(path7.join(candidate, "dist", "index.js"))) {
|
|
179
640
|
return candidate;
|
|
180
641
|
}
|
|
181
642
|
}
|
|
@@ -185,29 +646,33 @@ async function resolveEslintPluginSource() {
|
|
|
185
646
|
}
|
|
186
647
|
async function bundleEslintPlugin(targetDir) {
|
|
187
648
|
const pluginSource = await resolveEslintPluginSource();
|
|
188
|
-
const pluginTarget =
|
|
189
|
-
await
|
|
190
|
-
await
|
|
191
|
-
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"));
|
|
192
653
|
}
|
|
193
654
|
async function patchPackageJson(targetDir, projectName) {
|
|
194
|
-
const packageJsonPath =
|
|
195
|
-
const pkg = JSON.parse(await
|
|
655
|
+
const packageJsonPath = path7.join(targetDir, "package.json");
|
|
656
|
+
const pkg = JSON.parse(await fs7.readFile(packageJsonPath, "utf8"));
|
|
196
657
|
pkg.name = projectName;
|
|
197
658
|
delete pkg.scripts?.arch;
|
|
198
659
|
if (pkg.devDependencies) {
|
|
199
660
|
pkg.devDependencies["eslint-plugin-next-arch"] = "file:./vendor/eslint-plugin-next-arch";
|
|
200
661
|
}
|
|
201
|
-
await
|
|
662
|
+
await fs7.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
|
|
202
663
|
`);
|
|
203
664
|
}
|
|
204
665
|
async function initCommand(projectName, options = {}) {
|
|
205
|
-
const baseDir =
|
|
666
|
+
const baseDir = path7.resolve(options.cwd ?? process.cwd());
|
|
206
667
|
intro("Creating new Next Architecture project...");
|
|
207
|
-
const
|
|
668
|
+
const selections = await promptInitSelections({
|
|
669
|
+
yes: options.yes,
|
|
670
|
+
noExamples: options.noExamples
|
|
671
|
+
});
|
|
672
|
+
const targetDir = path7.join(baseDir, projectName);
|
|
208
673
|
const templateDir = resolveAppTemplateDir();
|
|
209
|
-
if (await
|
|
210
|
-
const shouldContinue = await
|
|
674
|
+
if (await fs7.pathExists(targetDir)) {
|
|
675
|
+
const shouldContinue = await confirm2({
|
|
211
676
|
message: "Directory already exists. Continue and merge files?"
|
|
212
677
|
});
|
|
213
678
|
if (!shouldContinue) {
|
|
@@ -215,41 +680,77 @@ async function initCommand(projectName, options = {}) {
|
|
|
215
680
|
return;
|
|
216
681
|
}
|
|
217
682
|
}
|
|
218
|
-
|
|
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)}...`);
|
|
219
688
|
await copyProjectTemplate(templateDir, targetDir);
|
|
220
689
|
await bundleEslintPlugin(targetDir);
|
|
221
690
|
await patchPackageJson(targetDir, projectName);
|
|
222
|
-
await
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
691
|
+
await applyPackageSelections(targetDir, selections);
|
|
692
|
+
await fs7.writeFile(path7.join(targetDir, ".npmrc"), "ignore-workspace=true\n");
|
|
693
|
+
log3.success(`Project "${projectName}" created`);
|
|
694
|
+
log3.info(` cd ${projectName}`);
|
|
695
|
+
log3.info(" npm install");
|
|
696
|
+
log3.info(" npm run dev");
|
|
697
|
+
if (selections.withExamples) {
|
|
698
|
+
log3.info(" See src/features/_examples/ for commented package examples");
|
|
699
|
+
}
|
|
227
700
|
outro("Done!");
|
|
228
701
|
}
|
|
229
702
|
|
|
230
703
|
// src/index.ts
|
|
231
704
|
console.log(chalk.blue("Next Architecture CLI"));
|
|
232
|
-
program.name("next-arch").description("CLI for Next.js Feature-Sliced Architecture").version("0.
|
|
233
|
-
program.command("init <projectName>").description("Create a new project with Next Architecture").option("-C, --cwd <path>", "directory where the project folder will be created").
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
705
|
+
program.name("next-arch").description("CLI for Next.js Feature-Sliced Architecture").version("0.2.0");
|
|
706
|
+
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(
|
|
707
|
+
async (projectName, options) => {
|
|
708
|
+
try {
|
|
709
|
+
await initCommand(projectName, {
|
|
710
|
+
cwd: options.cwd,
|
|
711
|
+
yes: options.yes,
|
|
712
|
+
noExamples: options.examples === false
|
|
713
|
+
});
|
|
714
|
+
} catch (error) {
|
|
715
|
+
cancel3(error instanceof Error ? error.message : "Init failed");
|
|
716
|
+
process.exit(1);
|
|
717
|
+
}
|
|
239
718
|
}
|
|
240
|
-
|
|
241
|
-
program.command("
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
719
|
+
);
|
|
720
|
+
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(
|
|
721
|
+
async (name, options) => {
|
|
722
|
+
try {
|
|
723
|
+
const projectRoot = options.cwd ? path8.resolve(options.cwd) : process.cwd();
|
|
724
|
+
await pageCommand(name, projectRoot, {
|
|
725
|
+
force: options.force,
|
|
726
|
+
yes: options.yes,
|
|
727
|
+
preset: options.preset
|
|
728
|
+
});
|
|
729
|
+
outro2("Done!");
|
|
730
|
+
} catch (error) {
|
|
731
|
+
cancel3(error instanceof Error ? error.message : "Page generation failed");
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
249
734
|
}
|
|
250
|
-
|
|
735
|
+
);
|
|
736
|
+
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(
|
|
737
|
+
async (type, name, options) => {
|
|
738
|
+
try {
|
|
739
|
+
const projectRoot = options.cwd ? path8.resolve(options.cwd) : process.cwd();
|
|
740
|
+
await generateCommand(type, name, projectRoot, {
|
|
741
|
+
force: options.force,
|
|
742
|
+
yes: options.yes,
|
|
743
|
+
preset: options.preset
|
|
744
|
+
});
|
|
745
|
+
outro2("Done!");
|
|
746
|
+
} catch (error) {
|
|
747
|
+
cancel3(error instanceof Error ? error.message : "Generate failed");
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
);
|
|
251
752
|
program.parseAsync(process.argv).catch((error) => {
|
|
252
|
-
|
|
753
|
+
log4.error(error instanceof Error ? error.message : "Unexpected error");
|
|
253
754
|
process.exit(1);
|
|
254
755
|
});
|
|
255
756
|
//# sourceMappingURL=index.js.map
|