create-softeneers-app 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/README.md +31 -0
- package/dist/args.js +96 -0
- package/dist/args.js.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.js +31 -0
- package/dist/prompts.js.map +1 -0
- package/dist/scaffold.js +130 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/templates.js +40 -0
- package/dist/templates.js.map +1 -0
- package/package.json +41 -0
- package/templates/next-fullstack/.env.example +18 -0
- package/templates/next-fullstack/README.md +54 -0
- package/templates/next-fullstack/apps/server/.env.example +8 -0
- package/templates/next-fullstack/apps/server/README.md +64 -0
- package/templates/next-fullstack/apps/server/config/config.cjs +24 -0
- package/templates/next-fullstack/apps/server/controller/carController.ts +62 -0
- package/templates/next-fullstack/apps/server/index.ts +50 -0
- package/templates/next-fullstack/apps/server/migrations/20260414160000-create-cars-table.js +39 -0
- package/templates/next-fullstack/apps/server/models/car.ts +70 -0
- package/templates/next-fullstack/apps/server/package.json +34 -0
- package/templates/next-fullstack/apps/server/routes/carRoutes.ts +18 -0
- package/templates/next-fullstack/apps/server/seeders/20260414160500-seed-cars.js +34 -0
- package/templates/next-fullstack/apps/server/tsconfig.json +14 -0
- package/templates/next-fullstack/apps/web/README.md +38 -0
- package/templates/next-fullstack/apps/web/app/components/Footer.tsx +19 -0
- package/templates/next-fullstack/apps/web/app/components/Navbar.tsx +34 -0
- package/templates/next-fullstack/apps/web/app/docs/page.tsx +170 -0
- package/templates/next-fullstack/apps/web/app/favicon.ico +0 -0
- package/templates/next-fullstack/apps/web/app/globals.css +1 -0
- package/templates/next-fullstack/apps/web/app/layout.tsx +29 -0
- package/templates/next-fullstack/apps/web/app/masini/page.tsx +269 -0
- package/templates/next-fullstack/apps/web/app/page.tsx +88 -0
- package/templates/next-fullstack/apps/web/eslint.config.mjs +18 -0
- package/templates/next-fullstack/apps/web/next.config.ts +7 -0
- package/templates/next-fullstack/apps/web/package.json +26 -0
- package/templates/next-fullstack/apps/web/postcss.config.mjs +7 -0
- package/templates/next-fullstack/apps/web/public/file.svg +1 -0
- package/templates/next-fullstack/apps/web/public/globe.svg +1 -0
- package/templates/next-fullstack/apps/web/public/next.svg +1 -0
- package/templates/next-fullstack/apps/web/public/vercel.svg +1 -0
- package/templates/next-fullstack/apps/web/public/window.svg +1 -0
- package/templates/next-fullstack/apps/web/tsconfig.json +34 -0
- package/templates/next-fullstack/docker-compose.yml +23 -0
- package/templates/next-fullstack/package.json +25 -0
- package/templates/next-fullstack/pnpm-workspace.yaml +3 -0
- package/templates/next-fullstack/turbo.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# create-softeneers-app
|
|
2
|
+
|
|
3
|
+
The Softeneers Framework project generator. Run it to scaffold a new project
|
|
4
|
+
from one of the bundled templates:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx create-softeneers-app@latest my-app
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
**Implemented** for the `next-fullstack` template: interactive wizard, copy +
|
|
13
|
+
transform, `git init`, dependency install, and next-step output. Other templates
|
|
14
|
+
are listed in the wizard as "coming soon".
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx create-softeneers-app@latest my-app # interactive
|
|
18
|
+
npx create-softeneers-app@latest my-app --yes --pm npm # non-interactive
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Flags: `--template <slug>`, `--yes`/`-y`, `--no-install`, `--no-git`,
|
|
22
|
+
`--pm <pnpm|npm|yarn>`, `--help`, `--version`. See
|
|
23
|
+
[`../../docs/CLI-SPEC.md`](../../docs/CLI-SPEC.md) for the full contract.
|
|
24
|
+
|
|
25
|
+
## Local development
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run build -w create-softeneers-app # tsc + bundle templates → dist/ + templates/
|
|
29
|
+
node apps/cli/dist/index.js my-app --yes # run the built CLI
|
|
30
|
+
npm run dev -w create-softeneers-app -- my-app # run from source via tsx
|
|
31
|
+
```
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Minimal, dependency-free argv parsing for create-softeneers-app. */
|
|
2
|
+
const PMS = ["pnpm", "npm", "yarn"];
|
|
3
|
+
/**
|
|
4
|
+
* Detect the package manager the user invoked us with (via `npm_config_user_agent`,
|
|
5
|
+
* which npm/pnpm/yarn all set). Defaults to npm — the framework is npm-first and
|
|
6
|
+
* npm is always present with Node.
|
|
7
|
+
*/
|
|
8
|
+
export function detectPackageManager() {
|
|
9
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
10
|
+
const name = ua.split("/")[0];
|
|
11
|
+
if (name === "pnpm" || name === "yarn")
|
|
12
|
+
return name;
|
|
13
|
+
return "npm";
|
|
14
|
+
}
|
|
15
|
+
export function parseArgs(argv) {
|
|
16
|
+
const opts = {
|
|
17
|
+
yes: false,
|
|
18
|
+
install: true,
|
|
19
|
+
git: true,
|
|
20
|
+
packageManager: detectPackageManager(),
|
|
21
|
+
help: false,
|
|
22
|
+
version: false,
|
|
23
|
+
};
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg === undefined)
|
|
27
|
+
continue;
|
|
28
|
+
switch (arg) {
|
|
29
|
+
case "--yes":
|
|
30
|
+
case "-y":
|
|
31
|
+
opts.yes = true;
|
|
32
|
+
break;
|
|
33
|
+
case "--no-install":
|
|
34
|
+
opts.install = false;
|
|
35
|
+
break;
|
|
36
|
+
case "--no-git":
|
|
37
|
+
opts.git = false;
|
|
38
|
+
break;
|
|
39
|
+
case "--help":
|
|
40
|
+
case "-h":
|
|
41
|
+
opts.help = true;
|
|
42
|
+
break;
|
|
43
|
+
case "--version":
|
|
44
|
+
case "-v":
|
|
45
|
+
opts.version = true;
|
|
46
|
+
break;
|
|
47
|
+
case "--template":
|
|
48
|
+
case "-t":
|
|
49
|
+
opts.template = argv[++i];
|
|
50
|
+
break;
|
|
51
|
+
case "--pm": {
|
|
52
|
+
const pm = argv[++i];
|
|
53
|
+
if (!PMS.includes(pm)) {
|
|
54
|
+
throw new CliError(`--pm must be one of: ${PMS.join(", ")} (got "${pm}")`);
|
|
55
|
+
}
|
|
56
|
+
opts.packageManager = pm;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
default:
|
|
60
|
+
if (arg.startsWith("-")) {
|
|
61
|
+
throw new CliError(`Unknown option: ${arg}`);
|
|
62
|
+
}
|
|
63
|
+
if (opts.targetDir === undefined) {
|
|
64
|
+
opts.targetDir = arg;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new CliError(`Unexpected argument: ${arg}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return opts;
|
|
72
|
+
}
|
|
73
|
+
/** A user-facing error (bad args, dir exists). Exit code 1. */
|
|
74
|
+
export class CliError extends Error {
|
|
75
|
+
}
|
|
76
|
+
export const HELP_TEXT = `
|
|
77
|
+
create-softeneers-app — scaffold a Softeneers Framework project
|
|
78
|
+
|
|
79
|
+
Usage:
|
|
80
|
+
npx create-softeneers-app@latest [directory|.] [options]
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
-t, --template <name> Template to use (default: prompt; only "next-fullstack" for now)
|
|
84
|
+
-y, --yes Accept defaults, no prompts
|
|
85
|
+
--no-install Don't install dependencies
|
|
86
|
+
--no-git Don't run "git init"
|
|
87
|
+
--pm <npm|pnpm|yarn> Package manager (default: auto-detected, else npm)
|
|
88
|
+
-h, --help Show this help
|
|
89
|
+
-v, --version Show version
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
npx create-softeneers-app@latest my-app # scaffold into ./my-app
|
|
93
|
+
npx create-softeneers-app@latest . # scaffold into the current directory
|
|
94
|
+
npm create softeneers-app@latest my-app -- --yes
|
|
95
|
+
`;
|
|
96
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAqBvE,MAAM,GAAG,GAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC;IACnD,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACpD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,IAAI,GAAe;QACvB,GAAG,EAAE,KAAK;QACV,OAAO,EAAE,IAAI;QACb,GAAG,EAAE,IAAI;QACT,cAAc,EAAE,oBAAoB,EAAE;QACtC,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,KAAK;KACf,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,SAAS;YAAE,SAAS;QAChC,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,OAAO,CAAC;YACb,KAAK,IAAI;gBACP,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;gBAChB,MAAM;YACR,KAAK,cAAc;gBACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;gBACrB,MAAM;YACR,KAAK,UAAU;gBACb,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;gBACjB,MAAM;YACR,KAAK,QAAQ,CAAC;YACd,KAAK,IAAI;gBACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR,KAAK,WAAW,CAAC;YACjB,KAAK,IAAI;gBACP,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,MAAM;YACR,KAAK,YAAY,CAAC;YAClB,KAAK,IAAI;gBACP,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1B,MAAM;YACR,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACrB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAoB,CAAC,EAAE,CAAC;oBACxC,MAAM,IAAI,QAAQ,CAAC,wBAAwB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBAC7E,CAAC;gBACD,IAAI,CAAC,cAAc,GAAG,EAAoB,CAAC;gBAC3C,MAAM;YACR,CAAC;YACD;gBACE,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACxB,MAAM,IAAI,QAAQ,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;gBAC/C,CAAC;gBACD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;oBACjC,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,QAAQ,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;gBACpD,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+DAA+D;AAC/D,MAAM,OAAO,QAAS,SAAQ,KAAK;CAAG;AAEtC,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;CAmBxB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { basename, resolve } from "node:path";
|
|
4
|
+
import { intro, log, note, outro, spinner } from "@clack/prompts";
|
|
5
|
+
import { CliError, HELP_TEXT, parseArgs } from "./args.js";
|
|
6
|
+
import { promptProjectName, promptTemplate } from "./prompts.js";
|
|
7
|
+
import { assertTargetUsable, copyTemplate, gitInit, installDeps, toPackageName, transform, } from "./scaffold.js";
|
|
8
|
+
import { DEFAULT_TEMPLATE, findTemplate, resolveTemplateDir } from "./templates.js";
|
|
9
|
+
function version() {
|
|
10
|
+
try {
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
return require("../package.json").version;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return "0.0.0";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function main() {
|
|
19
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
20
|
+
if (opts.help) {
|
|
21
|
+
console.log(HELP_TEXT);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (opts.version) {
|
|
25
|
+
console.log(version());
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
intro("create-softeneers-app");
|
|
29
|
+
// 1. Project name + target directory. "." (or any path resolving to cwd) scaffolds in place.
|
|
30
|
+
const rawTarget = opts.targetDir ?? (opts.yes ? "my-app" : await promptProjectName());
|
|
31
|
+
const targetDir = resolve(process.cwd(), rawTarget);
|
|
32
|
+
const inCurrentDir = targetDir === process.cwd();
|
|
33
|
+
const projectName = basename(targetDir);
|
|
34
|
+
// 2. Template.
|
|
35
|
+
const slug = opts.template ?? (opts.yes ? DEFAULT_TEMPLATE : await promptTemplate());
|
|
36
|
+
const template = findTemplate(slug);
|
|
37
|
+
if (!template) {
|
|
38
|
+
throw new CliError(`Unknown template "${slug}".`);
|
|
39
|
+
}
|
|
40
|
+
if (!template.available) {
|
|
41
|
+
throw new CliError(`Template "${slug}" is not available yet. Try "next-fullstack".`);
|
|
42
|
+
}
|
|
43
|
+
const templateDir = resolveTemplateDir(slug);
|
|
44
|
+
if (!templateDir) {
|
|
45
|
+
throw new CliError(`Could not locate template files for "${slug}".`);
|
|
46
|
+
}
|
|
47
|
+
// 3. Generate.
|
|
48
|
+
assertTargetUsable(targetDir);
|
|
49
|
+
const pkgName = toPackageName(targetDir);
|
|
50
|
+
const s = spinner();
|
|
51
|
+
s.start(`Creating ${projectName}`);
|
|
52
|
+
copyTemplate(templateDir, targetDir);
|
|
53
|
+
transform(targetDir, projectName, pkgName, opts.packageManager);
|
|
54
|
+
s.stop(`Created ${projectName}`);
|
|
55
|
+
if (opts.git) {
|
|
56
|
+
if (gitInit(targetDir))
|
|
57
|
+
log.success("Initialized a git repository.");
|
|
58
|
+
else
|
|
59
|
+
log.warn("Skipped git init (git not available).");
|
|
60
|
+
}
|
|
61
|
+
if (opts.install) {
|
|
62
|
+
const inst = spinner();
|
|
63
|
+
inst.start(`Installing dependencies with ${opts.packageManager}`);
|
|
64
|
+
if (installDeps(targetDir, opts.packageManager)) {
|
|
65
|
+
inst.stop("Installed dependencies.");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
inst.stop("Dependency install failed — run it manually.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 4. Next steps.
|
|
72
|
+
const pm = opts.packageManager;
|
|
73
|
+
const steps = [
|
|
74
|
+
...(inCurrentDir ? [] : [`cd ${rawTarget}`]),
|
|
75
|
+
...(opts.install ? [] : [`${pm} install`]),
|
|
76
|
+
"docker compose up -d # start MySQL",
|
|
77
|
+
`${pm} run db:migrate`,
|
|
78
|
+
`${pm} run db:seed`,
|
|
79
|
+
`${pm} run dev`,
|
|
80
|
+
].join("\n");
|
|
81
|
+
note(steps, "Next steps");
|
|
82
|
+
outro("Web: http://localhost:3000 API: http://localhost:4000");
|
|
83
|
+
}
|
|
84
|
+
main().catch((err) => {
|
|
85
|
+
if (err instanceof CliError) {
|
|
86
|
+
console.error(`\nError: ${err.message}`);
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.error("\nUnexpected error:", err);
|
|
91
|
+
process.exitCode = 2;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cancel, isCancel, select, text } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_TEMPLATE, TEMPLATES } from "./templates.js";
|
|
3
|
+
function bail(value) {
|
|
4
|
+
if (isCancel(value)) {
|
|
5
|
+
cancel("Cancelled.");
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
export async function promptProjectName(initial) {
|
|
11
|
+
const value = await text({
|
|
12
|
+
message: "Project name:",
|
|
13
|
+
placeholder: "my-app",
|
|
14
|
+
initialValue: initial,
|
|
15
|
+
validate: (v) => (v.trim().length === 0 ? "Please enter a project name." : undefined),
|
|
16
|
+
});
|
|
17
|
+
return bail(value).trim();
|
|
18
|
+
}
|
|
19
|
+
export async function promptTemplate() {
|
|
20
|
+
const value = await select({
|
|
21
|
+
message: "What type of project do you want?",
|
|
22
|
+
initialValue: DEFAULT_TEMPLATE,
|
|
23
|
+
options: TEMPLATES.map((t) => ({
|
|
24
|
+
value: t.slug,
|
|
25
|
+
label: t.label,
|
|
26
|
+
hint: t.available ? t.hint : `${t.hint} — not available yet`,
|
|
27
|
+
})),
|
|
28
|
+
});
|
|
29
|
+
return bail(value);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE7D,SAAS,IAAI,CAAI,KAAiB;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAgB;IACtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;QACvB,OAAO,EAAE,eAAe;QACxB,WAAW,EAAE,QAAQ;QACrB,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC;KACtF,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;QACzB,OAAO,EAAE,mCAAmC;QAC5C,YAAY,EAAE,gBAAgB;QAC9B,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,IAAI;YACb,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,sBAAsB;SAC7D,CAAC,CAAC;KACJ,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC"}
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { CliError } from "./args.js";
|
|
5
|
+
/** Names excluded when copying a template (docs/CLI-SPEC.md → copy exclusions). */
|
|
6
|
+
const EXCLUDED_NAMES = new Set([
|
|
7
|
+
"node_modules",
|
|
8
|
+
".next",
|
|
9
|
+
"out",
|
|
10
|
+
"build",
|
|
11
|
+
"dist",
|
|
12
|
+
".turbo",
|
|
13
|
+
".git",
|
|
14
|
+
".DS_Store",
|
|
15
|
+
"package-lock.json",
|
|
16
|
+
"pnpm-lock.yaml",
|
|
17
|
+
"yarn.lock",
|
|
18
|
+
]);
|
|
19
|
+
function isExcluded(name) {
|
|
20
|
+
if (EXCLUDED_NAMES.has(name))
|
|
21
|
+
return true;
|
|
22
|
+
if (name.endsWith(".log"))
|
|
23
|
+
return true;
|
|
24
|
+
// Drop real env files but keep the examples.
|
|
25
|
+
if (name === ".env" || (name.startsWith(".env.") && name !== ".env.example")) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
/** npm package name from a directory name: lowercase, spaces/invalid → dashes. */
|
|
31
|
+
export function toPackageName(dir) {
|
|
32
|
+
return (basename(dir)
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
35
|
+
.replace(/^[-.]+|[-.]+$/g, "") || "app");
|
|
36
|
+
}
|
|
37
|
+
/** Entries allowed to pre-exist in the target (e.g. when scaffolding into "."). */
|
|
38
|
+
const ALLOWED_EXISTING = new Set([".git", ".DS_Store"]);
|
|
39
|
+
export function assertTargetUsable(targetDir) {
|
|
40
|
+
if (!existsSync(targetDir))
|
|
41
|
+
return;
|
|
42
|
+
const blocking = readdirSync(targetDir).filter((name) => !ALLOWED_EXISTING.has(name));
|
|
43
|
+
if (blocking.length > 0) {
|
|
44
|
+
throw new CliError(`Target directory "${targetDir}" is not empty.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Copy the template into the target, applying exclusions. */
|
|
48
|
+
export function copyTemplate(templateDir, targetDir) {
|
|
49
|
+
mkdirSync(targetDir, { recursive: true });
|
|
50
|
+
cpSync(templateDir, targetDir, {
|
|
51
|
+
recursive: true,
|
|
52
|
+
filter: (src) => !isExcluded(basename(src)),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const PM_FALLBACK_VERSION = {
|
|
56
|
+
npm: "10.9.0",
|
|
57
|
+
pnpm: "9.12.0",
|
|
58
|
+
yarn: "4.5.0",
|
|
59
|
+
};
|
|
60
|
+
/** Best-effort actual version of the chosen PM, else a sane pinned fallback. */
|
|
61
|
+
function detectPmVersion(pm) {
|
|
62
|
+
const res = spawnSync(pm, ["--version"], {
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
shell: process.platform === "win32",
|
|
65
|
+
});
|
|
66
|
+
const v = res.status === 0 ? String(res.stdout).trim() : "";
|
|
67
|
+
return /^\d+\.\d+\.\d+/.test(v) ? v : PM_FALLBACK_VERSION[pm];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Rewrite the root package.json: set "name" and pin "packageManager" to the
|
|
71
|
+
* chosen PM. The packageManager field is required by Turborepo to resolve the
|
|
72
|
+
* workspace (the template ships both npm `workspaces` and pnpm-workspace.yaml).
|
|
73
|
+
*/
|
|
74
|
+
function rewriteRootPackage(targetDir, pkgName, pm) {
|
|
75
|
+
const pkgPath = join(targetDir, "package.json");
|
|
76
|
+
if (!existsSync(pkgPath))
|
|
77
|
+
return;
|
|
78
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
79
|
+
pkg.name = pkgName;
|
|
80
|
+
pkg.packageManager = `${pm}@${detectPmVersion(pm)}`;
|
|
81
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
82
|
+
}
|
|
83
|
+
/** For every .env.example in the tree, create a sibling .env if missing. */
|
|
84
|
+
function generateEnvFiles(targetDir) {
|
|
85
|
+
const stack = [targetDir];
|
|
86
|
+
while (stack.length) {
|
|
87
|
+
const dir = stack.pop();
|
|
88
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
89
|
+
if (entry.name === "node_modules")
|
|
90
|
+
continue;
|
|
91
|
+
const full = join(dir, entry.name);
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
stack.push(full);
|
|
94
|
+
}
|
|
95
|
+
else if (entry.name === ".env.example") {
|
|
96
|
+
const envPath = join(dir, ".env");
|
|
97
|
+
if (!existsSync(envPath)) {
|
|
98
|
+
writeFileSync(envPath, readFileSync(full, "utf8"));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Replace {{PROJECT_NAME}} placeholders in the README. */
|
|
105
|
+
function substitutePlaceholders(targetDir, projectName) {
|
|
106
|
+
const readme = join(targetDir, "README.md");
|
|
107
|
+
if (!existsSync(readme))
|
|
108
|
+
return;
|
|
109
|
+
const next = readFileSync(readme, "utf8").replaceAll("{{PROJECT_NAME}}", projectName);
|
|
110
|
+
writeFileSync(readme, next);
|
|
111
|
+
}
|
|
112
|
+
export function transform(targetDir, projectName, pkgName, pm) {
|
|
113
|
+
rewriteRootPackage(targetDir, pkgName, pm);
|
|
114
|
+
generateEnvFiles(targetDir);
|
|
115
|
+
substitutePlaceholders(targetDir, projectName);
|
|
116
|
+
}
|
|
117
|
+
function run(cmd, args, cwd) {
|
|
118
|
+
const res = spawnSync(cmd, args, { cwd, stdio: "ignore", shell: process.platform === "win32" });
|
|
119
|
+
return res.status === 0;
|
|
120
|
+
}
|
|
121
|
+
export function gitInit(targetDir) {
|
|
122
|
+
if (!run("git", ["init", "-q"], targetDir))
|
|
123
|
+
return false;
|
|
124
|
+
run("git", ["add", "-A"], targetDir);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
export function installDeps(targetDir, pm) {
|
|
128
|
+
return run(pm, ["install"], targetDir);
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/**
|
|
5
|
+
* The template registry. Only `next-fullstack` is generatable today; the rest
|
|
6
|
+
* are advertised so the wizard reflects the intended menu (docs/CLI-SPEC.md)
|
|
7
|
+
* and light up as they land.
|
|
8
|
+
*/
|
|
9
|
+
export const TEMPLATES = [
|
|
10
|
+
{
|
|
11
|
+
slug: "next-fullstack",
|
|
12
|
+
label: "Fullstack (Next.js + Express + Sequelize + MySQL)",
|
|
13
|
+
hint: "web + server monorepo, Docker MySQL",
|
|
14
|
+
available: true,
|
|
15
|
+
},
|
|
16
|
+
{ slug: "tanstack-start", label: "TanStack Start", hint: "coming soon", available: false },
|
|
17
|
+
{ slug: "hono-api", label: "Hono API only", hint: "coming soon", available: false },
|
|
18
|
+
{ slug: "express-api", label: "Express API only", hint: "coming soon", available: false },
|
|
19
|
+
{ slug: "minimal", label: "Minimal", hint: "coming soon", available: false },
|
|
20
|
+
];
|
|
21
|
+
export const DEFAULT_TEMPLATE = "next-fullstack";
|
|
22
|
+
export function findTemplate(slug) {
|
|
23
|
+
return TEMPLATES.find((t) => t.slug === slug);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the on-disk directory for a template, in priority order:
|
|
27
|
+
* 1. bundled with the package (<pkg>/templates/<slug>) — published layout
|
|
28
|
+
* 2. the monorepo templates dir (<pkg>/../../templates/<slug>) — local dev
|
|
29
|
+
* Returns the first that exists, or undefined.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveTemplateDir(slug) {
|
|
32
|
+
const here = dirname(fileURLToPath(import.meta.url)); // src/ (tsx) or dist/ (built)
|
|
33
|
+
const pkgRoot = resolve(here, ".."); // apps/cli
|
|
34
|
+
const candidates = [
|
|
35
|
+
resolve(pkgRoot, "templates", slug),
|
|
36
|
+
resolve(pkgRoot, "..", "..", "templates", slug),
|
|
37
|
+
];
|
|
38
|
+
return candidates.find((dir) => existsSync(dir) && statSync(dir).isDirectory());
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAUzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC;QACE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,mDAAmD;QAC1D,IAAI,EAAE,qCAAqC;QAC3C,SAAS,EAAE,IAAI;KAChB;IACD,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE;IAC1F,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE;IACnF,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE;IACzF,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE;CAC7E,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAEjD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,8BAA8B;IACpF,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW;IAChD,MAAM,UAAU,GAAG;QACjB,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC;QACnC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC;KAChD,CAAC;IACF,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAClF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-softeneers-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a new Softeneers Framework project from a template.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-softeneers-app": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-templates.mjs",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
17
|
+
"test": "node --test test/*.test.mjs",
|
|
18
|
+
"clean": "rm -rf dist templates",
|
|
19
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"softeneers",
|
|
26
|
+
"scaffold",
|
|
27
|
+
"generator",
|
|
28
|
+
"create-app"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@clack/prompts": "^0.7.0"
|
|
33
|
+
},
|
|
34
|
+
"author": "Tudor Damian",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/tunder007/monolith_demo.git",
|
|
38
|
+
"directory": "apps/cli"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/tunder007/monolith_demo#readme"
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Root environment for the generated project.
|
|
2
|
+
# Copied to .env by create-softeneers-app. Per-app values also live in
|
|
3
|
+
# apps/server/.env.example. Keep these in sync with docker-compose.yml.
|
|
4
|
+
|
|
5
|
+
# --- Web (apps/web) ---
|
|
6
|
+
# Base URL the browser app uses to reach the API.
|
|
7
|
+
NEXT_PUBLIC_API_URL=http://localhost:4000
|
|
8
|
+
|
|
9
|
+
# --- Server (apps/server) ---
|
|
10
|
+
PORT=4000
|
|
11
|
+
FRONTEND_URL=http://localhost:3000
|
|
12
|
+
|
|
13
|
+
# --- Database (must match docker-compose.yml) ---
|
|
14
|
+
DB_HOST=127.0.0.1
|
|
15
|
+
DB_PORT=3306
|
|
16
|
+
DB_NAME=app_dev
|
|
17
|
+
DB_USER=root
|
|
18
|
+
DB_PASSWORD=
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A fullstack app generated with
|
|
4
|
+
[`create-softeneers-app`](https://www.npmjs.com/package/create-softeneers-app):
|
|
5
|
+
**Next.js** web client + **Express / Sequelize / MySQL** JSON API, in a
|
|
6
|
+
Turborepo monorepo that works with **npm** or **pnpm**.
|
|
7
|
+
|
|
8
|
+
> `{{PROJECT_NAME}}` is replaced with your project name at generation time.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
apps/
|
|
14
|
+
web/ Next.js 16 + React 19 + Tailwind v4 → http://localhost:3000
|
|
15
|
+
server/ Express 5 + Sequelize 6 + mysql2 → http://localhost:4000
|
|
16
|
+
docker-compose.yml MySQL 8 for local development
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Install dependencies (npm shown; pnpm works too)
|
|
23
|
+
npm install
|
|
24
|
+
|
|
25
|
+
# 2. Configure environment
|
|
26
|
+
cp .env.example .env
|
|
27
|
+
cp apps/server/.env.example apps/server/.env
|
|
28
|
+
|
|
29
|
+
# 3. Start the database
|
|
30
|
+
docker compose up -d
|
|
31
|
+
|
|
32
|
+
# 4. Run migrations + seed
|
|
33
|
+
npm run db:migrate
|
|
34
|
+
npm run db:seed
|
|
35
|
+
|
|
36
|
+
# 5. Start web + server together
|
|
37
|
+
npm run dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- Web app: <http://localhost:3000>
|
|
41
|
+
- API health check: <http://localhost:4000/health>
|
|
42
|
+
- Cars API: <http://localhost:4000/api/cars>
|
|
43
|
+
|
|
44
|
+
## Scripts
|
|
45
|
+
|
|
46
|
+
All scripts run through Turborepo, so they behave identically under npm or pnpm.
|
|
47
|
+
|
|
48
|
+
| Command | Description |
|
|
49
|
+
| -------------------- | ---------------------------------- |
|
|
50
|
+
| `npm run dev` | Run web + server |
|
|
51
|
+
| `npm run build` | Build all apps |
|
|
52
|
+
| `npm run db:migrate` | Run Sequelize migrations (server) |
|
|
53
|
+
| `npm run db:seed` | Seed the database (server) |
|
|
54
|
+
| `npm run db:reset` | Undo, re-migrate and re-seed |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Backend Quick Start
|
|
2
|
+
|
|
3
|
+
Backend-ul este un API Express + Sequelize + MySQL pentru CRUD pe `cars`.
|
|
4
|
+
|
|
5
|
+
## 1) Instaleaza dependintele
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd backend
|
|
9
|
+
npm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 2) Creeaza baza de date MySQL (generic CLI)
|
|
13
|
+
|
|
14
|
+
Inlocuieste placeholder-ele cu valorile tale:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mysql -h <DB_HOST> -P <DB_PORT> -u <DB_USER> -p -e "CREATE DATABASE IF NOT EXISTS monolith_demo_dev;"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Exemplu uzual local:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
mysql -h 127.0.0.1 -P 3306 -u root -p -e "CREATE DATABASE IF NOT EXISTS monolith_demo_dev;"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 3) Configureaza `.env` din `.env.example`
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cp .env.example .env
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Editeaza apoi `./.env` cu datele tale reale de conexiune MySQL.
|
|
33
|
+
|
|
34
|
+
## 4) Ruleaza migration + seed
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run db:migrate
|
|
38
|
+
npm run db:seed
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Migration creeaza tabela `cars`, seed adauga date demo.
|
|
42
|
+
|
|
43
|
+
## 5) Porneste serverul backend
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Serverul ruleaza pe `http://localhost:4000`.
|
|
50
|
+
|
|
51
|
+
## 6) Verifica rapid API-ul
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
curl http://localhost:4000/health
|
|
55
|
+
curl http://localhost:4000/api/cars
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Comenzi utile
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm run db:reset
|
|
62
|
+
npm run build
|
|
63
|
+
npm run start
|
|
64
|
+
```
|