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.
Files changed (48) hide show
  1. package/README.md +31 -0
  2. package/dist/args.js +96 -0
  3. package/dist/args.js.map +1 -0
  4. package/dist/index.js +94 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/prompts.js +31 -0
  7. package/dist/prompts.js.map +1 -0
  8. package/dist/scaffold.js +130 -0
  9. package/dist/scaffold.js.map +1 -0
  10. package/dist/templates.js +40 -0
  11. package/dist/templates.js.map +1 -0
  12. package/package.json +41 -0
  13. package/templates/next-fullstack/.env.example +18 -0
  14. package/templates/next-fullstack/README.md +54 -0
  15. package/templates/next-fullstack/apps/server/.env.example +8 -0
  16. package/templates/next-fullstack/apps/server/README.md +64 -0
  17. package/templates/next-fullstack/apps/server/config/config.cjs +24 -0
  18. package/templates/next-fullstack/apps/server/controller/carController.ts +62 -0
  19. package/templates/next-fullstack/apps/server/index.ts +50 -0
  20. package/templates/next-fullstack/apps/server/migrations/20260414160000-create-cars-table.js +39 -0
  21. package/templates/next-fullstack/apps/server/models/car.ts +70 -0
  22. package/templates/next-fullstack/apps/server/package.json +34 -0
  23. package/templates/next-fullstack/apps/server/routes/carRoutes.ts +18 -0
  24. package/templates/next-fullstack/apps/server/seeders/20260414160500-seed-cars.js +34 -0
  25. package/templates/next-fullstack/apps/server/tsconfig.json +14 -0
  26. package/templates/next-fullstack/apps/web/README.md +38 -0
  27. package/templates/next-fullstack/apps/web/app/components/Footer.tsx +19 -0
  28. package/templates/next-fullstack/apps/web/app/components/Navbar.tsx +34 -0
  29. package/templates/next-fullstack/apps/web/app/docs/page.tsx +170 -0
  30. package/templates/next-fullstack/apps/web/app/favicon.ico +0 -0
  31. package/templates/next-fullstack/apps/web/app/globals.css +1 -0
  32. package/templates/next-fullstack/apps/web/app/layout.tsx +29 -0
  33. package/templates/next-fullstack/apps/web/app/masini/page.tsx +269 -0
  34. package/templates/next-fullstack/apps/web/app/page.tsx +88 -0
  35. package/templates/next-fullstack/apps/web/eslint.config.mjs +18 -0
  36. package/templates/next-fullstack/apps/web/next.config.ts +7 -0
  37. package/templates/next-fullstack/apps/web/package.json +26 -0
  38. package/templates/next-fullstack/apps/web/postcss.config.mjs +7 -0
  39. package/templates/next-fullstack/apps/web/public/file.svg +1 -0
  40. package/templates/next-fullstack/apps/web/public/globe.svg +1 -0
  41. package/templates/next-fullstack/apps/web/public/next.svg +1 -0
  42. package/templates/next-fullstack/apps/web/public/vercel.svg +1 -0
  43. package/templates/next-fullstack/apps/web/public/window.svg +1 -0
  44. package/templates/next-fullstack/apps/web/tsconfig.json +34 -0
  45. package/templates/next-fullstack/docker-compose.yml +23 -0
  46. package/templates/next-fullstack/package.json +25 -0
  47. package/templates/next-fullstack/pnpm-workspace.yaml +3 -0
  48. 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
@@ -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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"}
@@ -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"}
@@ -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,8 @@
1
+ PORT=4000
2
+ FRONTEND_URL=http://localhost:3000
3
+
4
+ DB_HOST=127.0.0.1
5
+ DB_PORT=3306
6
+ DB_NAME=monolith_demo_dev
7
+ DB_USER=root
8
+ DB_PASSWORD=
@@ -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
+ ```