@yousxlfs/next-arch 0.1.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.d.ts +2 -0
- package/dist/index.js +255 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
- package/templates/app/AGENTS.md +5 -0
- package/templates/app/CLAUDE.md +1 -0
- package/templates/app/README.md +36 -0
- package/templates/app/components.json +25 -0
- package/templates/app/eslint.config.mjs +33 -0
- package/templates/app/next-env.d.ts +6 -0
- package/templates/app/next.config.ts +7 -0
- package/templates/app/package.json +37 -0
- package/templates/app/postcss.config.mjs +7 -0
- package/templates/app/public/file.svg +1 -0
- package/templates/app/public/globe.svg +1 -0
- package/templates/app/public/next.svg +1 -0
- package/templates/app/public/vercel.svg +1 -0
- package/templates/app/public/window.svg +1 -0
- package/templates/app/src/app/favicon.ico +0 -0
- package/templates/app/src/app/globals.css +130 -0
- package/templates/app/src/app/layout.tsx +33 -0
- package/templates/app/src/app/page.tsx +6 -0
- package/templates/app/src/components/ui/button.tsx +67 -0
- package/templates/app/src/features/demo/actions/submitDemo.ts +5 -0
- package/templates/app/src/features/demo/api/fetchDemo.ts +3 -0
- package/templates/app/src/features/demo/index.ts +4 -0
- package/templates/app/src/features/demo/lib/formatDemo.ts +3 -0
- package/templates/app/src/features/demo/model/index.ts +2 -0
- package/templates/app/src/features/demo/model/types.ts +7 -0
- package/templates/app/src/features/demo/ui/FeatureDemo.tsx +18 -0
- package/templates/app/src/lib/utils.ts +1 -0
- package/templates/app/src/shared/lib/utils.ts +6 -0
- package/templates/app/src/shared/ui/index.ts +2 -0
- package/templates/app/src/views/HomeView/index.tsx +21 -0
- package/templates/app/tsconfig.json +40 -0
- package/templates/entity/index.ts +1 -0
- package/templates/entity/model/types.ts +3 -0
- package/templates/entity/ui/{{Name}}Card.tsx +8 -0
- package/templates/feature/actions/submit{{Name}}.ts +5 -0
- package/templates/feature/api/fetch{{Name}}.ts +3 -0
- package/templates/feature/index.ts +4 -0
- package/templates/feature/lib/format{{Name}}.ts +3 -0
- package/templates/feature/model/index.ts +5 -0
- package/templates/feature/model/types.ts +7 -0
- package/templates/feature/ui/{{Name}}.tsx +10 -0
- package/templates/view/index.tsx +12 -0
- package/templates/widget/index.ts +1 -0
- package/templates/widget/ui/{{Name}}.tsx +9 -0
- package/vendor/eslint-plugin-next-arch/dist/index.d.ts +50 -0
- package/vendor/eslint-plugin-next-arch/dist/index.js +386 -0
- package/vendor/eslint-plugin-next-arch/dist/index.js.map +1 -0
- package/vendor/eslint-plugin-next-arch/package.json +55 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { program } from "commander";
|
|
5
|
+
import { cancel, log as log3, outro as outro2 } from "@clack/prompts";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import path5 from "path";
|
|
8
|
+
|
|
9
|
+
// src/commands/generate.ts
|
|
10
|
+
import { log } from "@clack/prompts";
|
|
11
|
+
import fs2 from "fs-extra";
|
|
12
|
+
import path2 from "path";
|
|
13
|
+
|
|
14
|
+
// src/lib/naming.ts
|
|
15
|
+
function toPascalCase(value) {
|
|
16
|
+
return value.replace(/[-_/]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
17
|
+
}
|
|
18
|
+
function toKebabCase(value) {
|
|
19
|
+
return value.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
function assertValidSliceName(name) {
|
|
22
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Name must start with a letter and contain only letters, numbers, hyphens, or underscores."
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/lib/paths.ts
|
|
30
|
+
import path from "path";
|
|
31
|
+
import { fileURLToPath } from "url";
|
|
32
|
+
import fs from "fs-extra";
|
|
33
|
+
function findPackageRoot(startDir) {
|
|
34
|
+
let current = startDir;
|
|
35
|
+
while (current !== path.dirname(current)) {
|
|
36
|
+
const packageJsonPath = path.join(current, "package.json");
|
|
37
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
38
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
39
|
+
if (pkg.name === "next-arch") {
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
current = path.dirname(current);
|
|
44
|
+
}
|
|
45
|
+
throw new Error("next-arch package root not found.");
|
|
46
|
+
}
|
|
47
|
+
function getPackageRoot() {
|
|
48
|
+
return findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
49
|
+
}
|
|
50
|
+
function resolveTemplatesDir() {
|
|
51
|
+
const templatesDir = path.join(getPackageRoot(), "templates");
|
|
52
|
+
if (!fs.existsSync(templatesDir)) {
|
|
53
|
+
throw new Error("Templates directory not found. Reinstall next-arch.");
|
|
54
|
+
}
|
|
55
|
+
return templatesDir;
|
|
56
|
+
}
|
|
57
|
+
function resolveAppTemplateDir() {
|
|
58
|
+
const appTemplate = path.join(resolveTemplatesDir(), "app");
|
|
59
|
+
if (!fs.existsSync(path.join(appTemplate, "package.json"))) {
|
|
60
|
+
throw new Error("App template not found. Reinstall next-arch.");
|
|
61
|
+
}
|
|
62
|
+
return appTemplate;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/commands/generate.ts
|
|
66
|
+
var SLICE_TYPES = ["feature", "view", "widget", "entity"];
|
|
67
|
+
var TARGET_DIRS = {
|
|
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
|
+
}
|
|
83
|
+
async function renderTemplateDir(templateDir, targetDir, replacements) {
|
|
84
|
+
const created = [];
|
|
85
|
+
if (!await fs2.pathExists(templateDir)) {
|
|
86
|
+
throw new Error(`Template "${path2.basename(templateDir)}" not found.`);
|
|
87
|
+
}
|
|
88
|
+
const entries = await fs2.readdir(templateDir, { withFileTypes: true });
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const sourcePath = path2.join(templateDir, entry.name);
|
|
91
|
+
const renderedName = Object.entries(replacements).reduce(
|
|
92
|
+
(name, [from, to]) => name.replaceAll(from, to),
|
|
93
|
+
entry.name
|
|
94
|
+
);
|
|
95
|
+
const targetPath = path2.join(targetDir, renderedName);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
await fs2.ensureDir(targetPath);
|
|
98
|
+
created.push(...await renderTemplateDir(sourcePath, targetPath, replacements));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
await fs2.ensureDir(path2.dirname(targetPath));
|
|
102
|
+
let content = await fs2.readFile(sourcePath, "utf8");
|
|
103
|
+
for (const [from, to] of Object.entries(replacements)) {
|
|
104
|
+
content = content.replaceAll(from, to);
|
|
105
|
+
}
|
|
106
|
+
await fs2.writeFile(targetPath, content);
|
|
107
|
+
created.push(path2.relative(process.cwd(), targetPath));
|
|
108
|
+
}
|
|
109
|
+
return created;
|
|
110
|
+
}
|
|
111
|
+
async function generateCommand(type, name, projectRoot = process.cwd(), options = {}) {
|
|
112
|
+
const root = path2.resolve(projectRoot);
|
|
113
|
+
assertNextProject(root);
|
|
114
|
+
assertValidSliceName(name);
|
|
115
|
+
if (!isSliceType(type)) {
|
|
116
|
+
throw new Error(`Unknown type "${type}". Use: ${SLICE_TYPES.join(", ")}`);
|
|
117
|
+
}
|
|
118
|
+
const pascalName = toPascalCase(name);
|
|
119
|
+
const kebabName = toKebabCase(name);
|
|
120
|
+
const templatesDir = resolveTemplatesDir();
|
|
121
|
+
const templateDir = path2.join(templatesDir, type);
|
|
122
|
+
const targetDir = path2.join(root, "src", TARGET_DIRS[type], kebabName);
|
|
123
|
+
const previousCwd = process.cwd();
|
|
124
|
+
process.chdir(root);
|
|
125
|
+
try {
|
|
126
|
+
if (await fs2.pathExists(targetDir)) {
|
|
127
|
+
if (!options.force) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`"${type}" "${kebabName}" already exists at ${targetDir}. Use --force to overwrite.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
await fs2.remove(targetDir);
|
|
133
|
+
}
|
|
134
|
+
const replacements = {
|
|
135
|
+
"{{Name}}": pascalName,
|
|
136
|
+
"{{name}}": kebabName
|
|
137
|
+
};
|
|
138
|
+
const created = await renderTemplateDir(templateDir, targetDir, replacements);
|
|
139
|
+
log.success(`Created ${type} "${kebabName}" in ${root}`);
|
|
140
|
+
for (const file of created) {
|
|
141
|
+
log.info(` ${file}`);
|
|
142
|
+
}
|
|
143
|
+
if (type === "view") {
|
|
144
|
+
log.info(`Add to a route: import { ${pascalName} } from '@/views/${kebabName}';`);
|
|
145
|
+
}
|
|
146
|
+
} finally {
|
|
147
|
+
process.chdir(previousCwd);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/commands/init.ts
|
|
152
|
+
import { confirm, intro, log as log2, outro } from "@clack/prompts";
|
|
153
|
+
import fs4 from "fs-extra";
|
|
154
|
+
import path4 from "path";
|
|
155
|
+
|
|
156
|
+
// src/lib/copy.ts
|
|
157
|
+
import fs3 from "fs-extra";
|
|
158
|
+
import path3 from "path";
|
|
159
|
+
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".turbo", "dist"]);
|
|
160
|
+
async function copyProjectTemplate(sourceDir, targetDir) {
|
|
161
|
+
await fs3.copy(sourceDir, targetDir, {
|
|
162
|
+
filter(src) {
|
|
163
|
+
const relative = path3.relative(sourceDir, src);
|
|
164
|
+
if (!relative) return true;
|
|
165
|
+
return !relative.split(path3.sep).some((part) => EXCLUDED_DIRS.has(part));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/commands/init.ts
|
|
171
|
+
async function resolveEslintPluginSource() {
|
|
172
|
+
const packageRoot = getPackageRoot();
|
|
173
|
+
const candidates = [
|
|
174
|
+
path4.join(packageRoot, "vendor", "eslint-plugin-next-arch"),
|
|
175
|
+
path4.resolve(packageRoot, "..", "..", "packages", "eslint-plugin-next-arch")
|
|
176
|
+
];
|
|
177
|
+
for (const candidate of candidates) {
|
|
178
|
+
if (await fs4.pathExists(path4.join(candidate, "dist", "index.js"))) {
|
|
179
|
+
return candidate;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
throw new Error(
|
|
183
|
+
'eslint-plugin-next-arch is not available. Reinstall next-arch or run "pnpm build" in the monorepo.'
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
async function bundleEslintPlugin(targetDir) {
|
|
187
|
+
const pluginSource = await resolveEslintPluginSource();
|
|
188
|
+
const pluginTarget = path4.join(targetDir, "vendor", "eslint-plugin-next-arch");
|
|
189
|
+
await fs4.ensureDir(pluginTarget);
|
|
190
|
+
await fs4.copy(path4.join(pluginSource, "dist"), path4.join(pluginTarget, "dist"));
|
|
191
|
+
await fs4.copy(path4.join(pluginSource, "package.json"), path4.join(pluginTarget, "package.json"));
|
|
192
|
+
}
|
|
193
|
+
async function patchPackageJson(targetDir, projectName) {
|
|
194
|
+
const packageJsonPath = path4.join(targetDir, "package.json");
|
|
195
|
+
const pkg = JSON.parse(await fs4.readFile(packageJsonPath, "utf8"));
|
|
196
|
+
pkg.name = projectName;
|
|
197
|
+
delete pkg.scripts?.arch;
|
|
198
|
+
if (pkg.devDependencies) {
|
|
199
|
+
pkg.devDependencies["eslint-plugin-next-arch"] = "file:./vendor/eslint-plugin-next-arch";
|
|
200
|
+
}
|
|
201
|
+
await fs4.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
|
|
202
|
+
`);
|
|
203
|
+
}
|
|
204
|
+
async function initCommand(projectName, options = {}) {
|
|
205
|
+
const baseDir = path4.resolve(options.cwd ?? process.cwd());
|
|
206
|
+
intro("Creating new Next Architecture project...");
|
|
207
|
+
const targetDir = path4.join(baseDir, projectName);
|
|
208
|
+
const templateDir = resolveAppTemplateDir();
|
|
209
|
+
if (await fs4.pathExists(targetDir)) {
|
|
210
|
+
const shouldContinue = await confirm({
|
|
211
|
+
message: "Directory already exists. Continue and merge files?"
|
|
212
|
+
});
|
|
213
|
+
if (!shouldContinue) {
|
|
214
|
+
outro("Cancelled");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
log2.info(`Copying template from ${path4.basename(templateDir)}...`);
|
|
219
|
+
await copyProjectTemplate(templateDir, targetDir);
|
|
220
|
+
await bundleEslintPlugin(targetDir);
|
|
221
|
+
await patchPackageJson(targetDir, projectName);
|
|
222
|
+
await fs4.writeFile(path4.join(targetDir, ".npmrc"), "ignore-workspace=true\n");
|
|
223
|
+
log2.success(`Project "${projectName}" created`);
|
|
224
|
+
log2.info(` cd ${projectName}`);
|
|
225
|
+
log2.info(" pnpm install");
|
|
226
|
+
log2.info(" pnpm dev");
|
|
227
|
+
outro("Done!");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/index.ts
|
|
231
|
+
console.log(chalk.blue("Next Architecture CLI"));
|
|
232
|
+
program.name("next-arch").description("CLI for Next.js Feature-Sliced Architecture").version("0.1.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").action(async (projectName, options) => {
|
|
234
|
+
try {
|
|
235
|
+
await initCommand(projectName, { cwd: options.cwd });
|
|
236
|
+
} catch (error) {
|
|
237
|
+
cancel(error instanceof Error ? error.message : "Init failed");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
program.command("generate <type> <name>").alias("g").description("Generate feature, widget, entity, or view").option("-C, --cwd <path>", "path to Next.js project root (default: current directory)").option("-f, --force", "overwrite existing slice").action(async (type, name, options) => {
|
|
242
|
+
try {
|
|
243
|
+
const projectRoot = options.cwd ? path5.resolve(options.cwd) : process.cwd();
|
|
244
|
+
await generateCommand(type, name, projectRoot, { force: options.force });
|
|
245
|
+
outro2("Done!");
|
|
246
|
+
} catch (error) {
|
|
247
|
+
cancel(error instanceof Error ? error.message : "Generate failed");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
252
|
+
log3.error(error instanceof Error ? error.message : "Unexpected error");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
});
|
|
255
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/lib/naming.ts","../src/lib/paths.ts","../src/commands/init.ts","../src/lib/copy.ts"],"sourcesContent":["import { program } from 'commander';\nimport { cancel, log, outro } from '@clack/prompts';\nimport chalk from 'chalk';\nimport path from 'path';\nimport { generateCommand } from './commands/generate.js';\nimport { initCommand } from './commands/init.js';\n\nconsole.log(chalk.blue('Next Architecture CLI'));\n\nprogram\n .name('next-arch')\n .description('CLI for Next.js Feature-Sliced Architecture')\n .version('0.1.0');\n\nprogram\n .command('init <projectName>')\n .description('Create a new project with Next Architecture')\n .option('-C, --cwd <path>', 'directory where the project folder will be created')\n .action(async (projectName: string, options: { cwd?: string }) => {\n try {\n await initCommand(projectName, { cwd: options.cwd });\n } catch (error) {\n cancel(error instanceof Error ? error.message : 'Init failed');\n process.exit(1);\n }\n });\n\nprogram\n .command('generate <type> <name>')\n .alias('g')\n .description('Generate feature, widget, entity, or view')\n .option('-C, --cwd <path>', 'path to Next.js project root (default: current directory)')\n .option('-f, --force', 'overwrite existing slice')\n .action(async (type: string, name: string, options: { cwd?: string; force?: boolean }) => {\n try {\n const projectRoot = options.cwd ? path.resolve(options.cwd) : process.cwd();\n await generateCommand(type, name, projectRoot, { force: options.force });\n outro('Done!');\n } catch (error) {\n cancel(error instanceof Error ? error.message : 'Generate failed');\n process.exit(1);\n }\n });\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n log.error(error instanceof Error ? error.message : 'Unexpected error');\n process.exit(1);\n});\n","import { log } from '@clack/prompts';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { assertValidSliceName, toKebabCase, toPascalCase } from '../lib/naming.js';\nimport { resolveTemplatesDir } from '../lib/paths.js';\n\nconst SLICE_TYPES = ['feature', 'view', 'widget', 'entity'] as const;\ntype SliceType = (typeof SLICE_TYPES)[number];\n\nconst TARGET_DIRS: Record<SliceType, string> = {\n feature: 'features',\n view: 'views',\n widget: 'widgets',\n entity: 'entities',\n};\n\nfunction isSliceType(value: string): value is SliceType {\n return SLICE_TYPES.includes(value as SliceType);\n}\n\nfunction assertNextProject(cwd: string): void {\n const packageJson = path.join(cwd, 'package.json');\n const srcDir = path.join(cwd, 'src');\n\n if (!fs.existsSync(packageJson) || !fs.existsSync(srcDir)) {\n throw new Error('Run this command from the root of a Next Architecture project.');\n }\n}\n\nasync function renderTemplateDir(\n templateDir: string,\n targetDir: string,\n replacements: Record<string, string>,\n): Promise<string[]> {\n const created: string[] = [];\n\n if (!(await fs.pathExists(templateDir))) {\n throw new Error(`Template \"${path.basename(templateDir)}\" not found.`);\n }\n\n const entries = await fs.readdir(templateDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(templateDir, entry.name);\n const renderedName = Object.entries(replacements).reduce(\n (name, [from, to]) => name.replaceAll(from, to),\n entry.name,\n );\n const targetPath = path.join(targetDir, renderedName);\n\n if (entry.isDirectory()) {\n await fs.ensureDir(targetPath);\n created.push(...(await renderTemplateDir(sourcePath, targetPath, replacements)));\n continue;\n }\n\n await fs.ensureDir(path.dirname(targetPath));\n let content = await fs.readFile(sourcePath, 'utf8');\n for (const [from, to] of Object.entries(replacements)) {\n content = content.replaceAll(from, to);\n }\n await fs.writeFile(targetPath, content);\n created.push(path.relative(process.cwd(), targetPath));\n }\n\n return created;\n}\n\nexport async function generateCommand(\n type: string,\n name: string,\n projectRoot = process.cwd(),\n options: { force?: boolean } = {},\n): Promise<void> {\n const root = path.resolve(projectRoot);\n assertNextProject(root);\n assertValidSliceName(name);\n\n if (!isSliceType(type)) {\n throw new Error(`Unknown type \"${type}\". Use: ${SLICE_TYPES.join(', ')}`);\n }\n\n const pascalName = toPascalCase(name);\n const kebabName = toKebabCase(name);\n const templatesDir = resolveTemplatesDir();\n const templateDir = path.join(templatesDir, type);\n const targetDir = path.join(root, 'src', TARGET_DIRS[type], kebabName);\n\n const previousCwd = process.cwd();\n process.chdir(root);\n\n try {\n if (await fs.pathExists(targetDir)) {\n if (!options.force) {\n throw new Error(\n `\"${type}\" \"${kebabName}\" already exists at ${targetDir}. Use --force to overwrite.`,\n );\n }\n await fs.remove(targetDir);\n }\n\n const replacements = {\n '{{Name}}': pascalName,\n '{{name}}': kebabName,\n };\n\n const created = await renderTemplateDir(templateDir, targetDir, replacements);\n\n log.success(`Created ${type} \"${kebabName}\" in ${root}`);\n for (const file of created) {\n log.info(` ${file}`);\n }\n\n if (type === 'view') {\n log.info(`Add to a route: import { ${pascalName} } from '@/views/${kebabName}';`);\n }\n } finally {\n process.chdir(previousCwd);\n }\n}\n","export function toPascalCase(value: string): string {\n return value\n .replace(/[-_/]+/g, ' ')\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .split(' ')\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('');\n}\n\nexport function toKebabCase(value: string): string {\n return value\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_]+/g, '-')\n .toLowerCase();\n}\n\nexport function assertValidSliceName(name: string): void {\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {\n throw new Error(\n 'Name must start with a letter and contain only letters, numbers, hyphens, or underscores.',\n );\n }\n}\n","import path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs-extra';\n\nfunction findPackageRoot(startDir: string): string {\n let current = startDir;\n\n while (current !== path.dirname(current)) {\n const packageJsonPath = path.join(current, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { name?: string };\n if (pkg.name === 'next-arch') {\n return current;\n }\n }\n current = path.dirname(current);\n }\n\n throw new Error('next-arch package root not found.');\n}\n\nexport function getPackageRoot(): string {\n return findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));\n}\n\nexport function resolveTemplatesDir(): string {\n const templatesDir = path.join(getPackageRoot(), 'templates');\n if (!fs.existsSync(templatesDir)) {\n throw new Error('Templates directory not found. Reinstall next-arch.');\n }\n return templatesDir;\n}\n\nexport function resolveAppTemplateDir(): string {\n const appTemplate = path.join(resolveTemplatesDir(), 'app');\n if (!fs.existsSync(path.join(appTemplate, 'package.json'))) {\n throw new Error('App template not found. Reinstall next-arch.');\n }\n return appTemplate;\n}\n","import { confirm, intro, log, outro } from '@clack/prompts';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { copyProjectTemplate } from '../lib/copy.js';\nimport { getPackageRoot, resolveAppTemplateDir } from '../lib/paths.js';\n\nasync function resolveEslintPluginSource(): Promise<string> {\n const packageRoot = getPackageRoot();\n const candidates = [\n path.join(packageRoot, 'vendor', 'eslint-plugin-next-arch'),\n path.resolve(packageRoot, '..', '..', 'packages', 'eslint-plugin-next-arch'),\n ];\n\n for (const candidate of candidates) {\n if (await fs.pathExists(path.join(candidate, 'dist', 'index.js'))) {\n return candidate;\n }\n }\n\n throw new Error(\n 'eslint-plugin-next-arch is not available. Reinstall next-arch or run \"pnpm build\" in the monorepo.',\n );\n}\n\nasync function bundleEslintPlugin(targetDir: string): Promise<void> {\n const pluginSource = await resolveEslintPluginSource();\n const pluginTarget = path.join(targetDir, 'vendor', 'eslint-plugin-next-arch');\n\n await fs.ensureDir(pluginTarget);\n await fs.copy(path.join(pluginSource, 'dist'), path.join(pluginTarget, 'dist'));\n await fs.copy(path.join(pluginSource, 'package.json'), path.join(pluginTarget, 'package.json'));\n}\n\nasync function patchPackageJson(targetDir: string, projectName: string): Promise<void> {\n const packageJsonPath = path.join(targetDir, 'package.json');\n const pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as {\n name: string;\n scripts?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n pkg.name = projectName;\n delete pkg.scripts?.arch;\n if (pkg.devDependencies) {\n pkg.devDependencies['eslint-plugin-next-arch'] = 'file:./vendor/eslint-plugin-next-arch';\n }\n\n await fs.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\\n`);\n}\n\nexport async function initCommand(\n projectName: string,\n options: { cwd?: string } = {},\n): Promise<void> {\n const baseDir = path.resolve(options.cwd ?? process.cwd());\n intro('Creating new Next Architecture project...');\n\n const targetDir = path.join(baseDir, projectName);\n const templateDir = resolveAppTemplateDir();\n\n if (await fs.pathExists(targetDir)) {\n const shouldContinue = await confirm({\n message: 'Directory already exists. Continue and merge files?',\n });\n\n if (!shouldContinue) {\n outro('Cancelled');\n return;\n }\n }\n\n log.info(`Copying template from ${path.basename(templateDir)}...`);\n await copyProjectTemplate(templateDir, targetDir);\n await bundleEslintPlugin(targetDir);\n await patchPackageJson(targetDir, projectName);\n await fs.writeFile(path.join(targetDir, '.npmrc'), 'ignore-workspace=true\\n');\n\n log.success(`Project \"${projectName}\" created`);\n log.info(` cd ${projectName}`);\n log.info(' pnpm install');\n log.info(' pnpm dev');\n outro('Done!');\n}\n","import fs from 'fs-extra';\nimport path from 'path';\n\nconst EXCLUDED_DIRS = new Set(['node_modules', '.next', '.turbo', 'dist']);\n\nexport async function copyProjectTemplate(\n sourceDir: string,\n targetDir: string,\n): Promise<void> {\n await fs.copy(sourceDir, targetDir, {\n filter(src) {\n const relative = path.relative(sourceDir, src);\n if (!relative) return true;\n\n return !relative.split(path.sep).some((part) => EXCLUDED_DIRS.has(part));\n },\n });\n}\n\nexport async function replaceInFile(\n filePath: string,\n replacements: Record<string, string>,\n): Promise<void> {\n if (!(await fs.pathExists(filePath))) return;\n\n let content = await fs.readFile(filePath, 'utf8');\n for (const [from, to] of Object.entries(replacements)) {\n content = content.replaceAll(from, to);\n }\n await fs.writeFile(filePath, content);\n}\n"],"mappings":"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","names":["log","outro","path","fs","path","path","fs","log","fs","path","fs","path","path","fs","log","path","outro","log"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yousxlfs/next-arch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for scaffolding Next.js 16 projects with Feature-Sliced Design",
|
|
5
|
+
"author": "yousxlfs",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/yousxlfs/next-archi#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/yousxlfs/next-archi.git",
|
|
11
|
+
"directory": "packages/next-arch"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/yousxlfs/next-archi/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nextjs",
|
|
18
|
+
"next",
|
|
19
|
+
"cli",
|
|
20
|
+
"feature-sliced-design",
|
|
21
|
+
"fsd",
|
|
22
|
+
"architecture",
|
|
23
|
+
"scaffold"
|
|
24
|
+
],
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"bin": {
|
|
27
|
+
"next-arch": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"templates",
|
|
32
|
+
"vendor"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup && node scripts/sync-vendor.mjs",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"prepublishOnly": "pnpm build"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@clack/prompts": "^0.10.0",
|
|
41
|
+
"chalk": "^5.4.1",
|
|
42
|
+
"commander": "^13.1.0",
|
|
43
|
+
"fs-extra": "^11.3.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/fs-extra": "^11.0.4",
|
|
47
|
+
"@types/node": "^20",
|
|
48
|
+
"tsup": "^8.4.0",
|
|
49
|
+
"typescript": "^5.8.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"type": "module"
|
|
58
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
2
|
+
# This is NOT the Next.js you know
|
|
3
|
+
|
|
4
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
5
|
+
<!-- END:nextjs-agent-rules -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
import nextArch from "eslint-plugin-next-arch";
|
|
5
|
+
|
|
6
|
+
const eslintConfig = defineConfig([
|
|
7
|
+
...nextVitals,
|
|
8
|
+
...nextTs,
|
|
9
|
+
{
|
|
10
|
+
plugins: {
|
|
11
|
+
"next-arch": nextArch,
|
|
12
|
+
},
|
|
13
|
+
settings: {
|
|
14
|
+
"next-arch": {
|
|
15
|
+
srcDir: "src",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
"next-arch/no-cross-feature-imports": "error",
|
|
20
|
+
"next-arch/no-deep-imports": "error",
|
|
21
|
+
"next-arch/no-server-in-client": "error",
|
|
22
|
+
"next-arch/no-upward-imports": "error",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
globalIgnores([
|
|
26
|
+
".next/**",
|
|
27
|
+
"out/**",
|
|
28
|
+
"build/**",
|
|
29
|
+
"next-env.d.ts",
|
|
30
|
+
]),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@tanstack/react-query": "^5.101.2",
|
|
13
|
+
"class-variance-authority": "^0.7.1",
|
|
14
|
+
"clsx": "^2.1.1",
|
|
15
|
+
"lucide-react": "^1.22.0",
|
|
16
|
+
"next": "16.2.9",
|
|
17
|
+
"radix-ui": "^1.6.0",
|
|
18
|
+
"react": "19.2.4",
|
|
19
|
+
"react-dom": "19.2.4",
|
|
20
|
+
"shadcn": "^4.12.0",
|
|
21
|
+
"tailwind-merge": "^3.6.0",
|
|
22
|
+
"tw-animate-css": "^1.4.0",
|
|
23
|
+
"zod": "^4.4.3",
|
|
24
|
+
"zustand": "^5.0.14"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@tailwindcss/postcss": "^4",
|
|
28
|
+
"@types/node": "^20",
|
|
29
|
+
"@types/react": "^19",
|
|
30
|
+
"@types/react-dom": "^19",
|
|
31
|
+
"eslint": "^9",
|
|
32
|
+
"eslint-config-next": "16.2.9",
|
|
33
|
+
"eslint-plugin-next-arch": "workspace:*",
|
|
34
|
+
"tailwindcss": "^4",
|
|
35
|
+
"typescript": "^5"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
Binary file
|