create-veap 0.1.2
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 +3 -0
- package/bin/veap.js +7 -0
- package/dist/commands/docker.d.ts +1 -0
- package/dist/commands/docker.js +55 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +111 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +1 -0
- package/dist/manifests.d.ts +22 -0
- package/dist/manifests.js +32 -0
- package/dist/mutate.d.ts +46 -0
- package/dist/mutate.js +127 -0
- package/dist/package-manager.d.ts +56 -0
- package/dist/package-manager.js +222 -0
- package/dist/project-name.d.ts +22 -0
- package/dist/project-name.js +107 -0
- package/dist/prompts.d.ts +20 -0
- package/dist/prompts.js +93 -0
- package/dist/scaffold.d.ts +26 -0
- package/dist/scaffold.js +62 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +29 -0
- package/package.json +45 -0
- package/stubs/docker/.dockerignore.stub +39 -0
- package/stubs/docker/Dockerfile.bun.stub +42 -0
- package/stubs/docker/Dockerfile.npm.stub +42 -0
- package/stubs/docker/Dockerfile.stub +49 -0
- package/stubs/docker/Dockerfile.yarn.stub +44 -0
- package/stubs/docker/compose.yml.stub +58 -0
- package/stubs/overlay-full/AGENTS.md.stub +63 -0
- package/stubs/overlay-full/app/[[...catchAll]]/page.tsx.stub +47 -0
- package/stubs/overlay-full/app/api/[...catchAll]/route.ts.stub +111 -0
- package/stubs/overlay-full/app/error.tsx.stub +34 -0
- package/stubs/overlay-full/app/globals.css.stub +124 -0
- package/stubs/overlay-full/app/layout.tsx.stub +71 -0
- package/stubs/overlay-full/app/not-found.tsx.stub +29 -0
- package/stubs/overlay-full/app/storage/[...path]/route.ts.stub +61 -0
- package/stubs/overlay-full/lib/plugins.gen.ts.stub +6 -0
- package/stubs/overlay-full/lib/veap.ts.stub +26 -0
- package/stubs/overlay-full/migrations/index.ts.stub +2 -0
- package/stubs/overlay-full/next.config.ts.stub +34 -0
- package/stubs/overlay-full/proxy.ts.stub +27 -0
package/README.md
ADDED
package/bin/veap.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initDockerConfig(targetDir?: string, pm?: "pnpm" | "npm" | "yarn" | "bun"): Promise<void>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
export async function initDockerConfig(targetDir, pm) {
|
|
6
|
+
const rootDir = targetDir || process.cwd();
|
|
7
|
+
console.log(`\nš³ Initializing Docker configuration in ${rootDir}...`);
|
|
8
|
+
// Read project name for compose.yml placeholders (like @veap/cli does)
|
|
9
|
+
let projectName = "veap-app";
|
|
10
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
11
|
+
if (fs.existsSync(pkgPath)) {
|
|
12
|
+
try {
|
|
13
|
+
projectName =
|
|
14
|
+
JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name || projectName;
|
|
15
|
+
}
|
|
16
|
+
catch (_) { }
|
|
17
|
+
}
|
|
18
|
+
const stubsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../stubs/docker");
|
|
19
|
+
if (!fs.existsSync(stubsDir)) {
|
|
20
|
+
console.error(`Error: Missing Docker stubs directory at ${stubsDir}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const files = [
|
|
24
|
+
{ src: "Dockerfile.stub", dest: "Dockerfile" },
|
|
25
|
+
{ src: ".dockerignore.stub", dest: ".dockerignore" },
|
|
26
|
+
{ src: "compose.yml.stub", dest: "compose.yml" },
|
|
27
|
+
];
|
|
28
|
+
const spin = ora("Generating Docker files...").start();
|
|
29
|
+
try {
|
|
30
|
+
for (const f of files) {
|
|
31
|
+
let content = fs.readFileSync(path.join(stubsDir, f.src), "utf-8");
|
|
32
|
+
// Replace project name in compose.yml
|
|
33
|
+
if (f.dest === "compose.yml") {
|
|
34
|
+
content = content.replace(/\{\{name\}\}/g, projectName);
|
|
35
|
+
}
|
|
36
|
+
fs.writeFileSync(path.join(rootDir, f.dest), content, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
// Copy the Dockerfile variant for the chosen package manager over the
|
|
39
|
+
// pnpm baseline shipped in the stubs.
|
|
40
|
+
if (pm && pm !== "pnpm") {
|
|
41
|
+
const variantPath = path.join(stubsDir, `Dockerfile.${pm}.stub`);
|
|
42
|
+
if (fs.existsSync(variantPath)) {
|
|
43
|
+
fs.writeFileSync(path.join(rootDir, "Dockerfile"), fs.readFileSync(variantPath, "utf-8"), "utf-8");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
spin.succeed("Docker configuration files initialized.");
|
|
47
|
+
console.log(" - Dockerfile");
|
|
48
|
+
console.log(" - compose.yml");
|
|
49
|
+
console.log(" - .dockerignore\n");
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
spin.fail("Failed to generate Docker files.");
|
|
53
|
+
console.error(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { processStubs } from "../utils.js";
|
|
6
|
+
import { applyPackageManager, pmInstall, pmRunCommand, resolvePackageManager, } from "../package-manager.js";
|
|
7
|
+
import { scaffoldNextApp } from "../scaffold.js";
|
|
8
|
+
import { promptConfirm, promptProjectName } from "../prompts.js";
|
|
9
|
+
import { parseProjectName, projectNameError } from "../project-name.js";
|
|
10
|
+
import { appendGitignore, clearAppDirectory, injectVeapDependencies, writeEnvFile, } from "../mutate.js";
|
|
11
|
+
export async function initProject(name, options) {
|
|
12
|
+
// ---- Resolve the project name: from the CLI argument or the interactive
|
|
13
|
+
// prompt (which resolves to a default on non-TTY, so scripts never hang).
|
|
14
|
+
let nameInput = (name ?? "").trim();
|
|
15
|
+
if (!nameInput) {
|
|
16
|
+
nameInput = await promptProjectName("my-veap-app", process.cwd());
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const error = projectNameError(nameInput, process.cwd());
|
|
20
|
+
if (error) {
|
|
21
|
+
console.error(`Error: ${error}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// npm name rules: `@scope/name` keeps the full name in package.json while
|
|
26
|
+
// the project folder receives the base name.
|
|
27
|
+
const parsed = parseProjectName(nameInput);
|
|
28
|
+
const projectName = parsed.folder;
|
|
29
|
+
const destDir = path.resolve(process.cwd(), projectName);
|
|
30
|
+
// ---- Docker is opt-in; ask when the flag was not given (TTY only).
|
|
31
|
+
let withDocker = options?.docker;
|
|
32
|
+
if (withDocker === undefined) {
|
|
33
|
+
withDocker = await promptConfirm("Include Docker configuration?", false);
|
|
34
|
+
}
|
|
35
|
+
// Resolve the package manager up front so the prompt runs before any
|
|
36
|
+
// scaffolding starts (cleaner UX than asking mid-scaffold).
|
|
37
|
+
const pm = await resolvePackageManager({ pm: options?.pm });
|
|
38
|
+
const overlayFolder = "../../stubs/overlay-full";
|
|
39
|
+
console.log(`š Initializing new Veap project (Full plugin-enabled): ${projectName} in ${destDir}...`);
|
|
40
|
+
try {
|
|
41
|
+
// ---- 1. Scaffold the Next.js shell with the official create-next-app.
|
|
42
|
+
// CNA owns the boilerplate (Next version, tsconfig, postcss, fonts,
|
|
43
|
+
// README, git init); it runs with explicit flags so it never prompts.
|
|
44
|
+
const spin = ora("Scaffolding Next.js app (create-next-app)...").start();
|
|
45
|
+
try {
|
|
46
|
+
scaffoldNextApp(projectName, { cwd: process.cwd(), pm });
|
|
47
|
+
spin.succeed("Next.js app scaffolded.");
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
spin.fail("create-next-app failed.");
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
const variables = { name: projectName };
|
|
54
|
+
// ---- 2. The overlay owns the whole app/ tree: a leftover CNA page would
|
|
55
|
+
// collide with the optional catch-all route (same specificity).
|
|
56
|
+
clearAppDirectory(destDir);
|
|
57
|
+
// ---- 3. Apply the veap overlay: only the files CNA cannot know about
|
|
58
|
+
// (veap layout, router catch-all, api pipeline, storage route, bootstrap,
|
|
59
|
+
// veap next.config...). Overlay files overwrite CNA counterparts.
|
|
60
|
+
const overlaySpin = ora("Applying Veap overlay files...").start();
|
|
61
|
+
const stubsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), overlayFolder);
|
|
62
|
+
processStubs(stubsDir, destDir, variables);
|
|
63
|
+
overlaySpin.succeed("Veap overlay applied.");
|
|
64
|
+
// ---- 4. Mutate the generated project: veap dependencies, starter .env
|
|
65
|
+
// (with a fresh random ENCRYPTION_KEY) and .gitignore additions. Scoped
|
|
66
|
+
// names are restored into package.json here (CNA derives the name from
|
|
67
|
+
// the folder and drops the scope).
|
|
68
|
+
const mutateSpin = ora("Configuring Veap (deps, .env)...").start();
|
|
69
|
+
injectVeapDependencies(destDir, parsed.packageName);
|
|
70
|
+
writeEnvFile(destDir);
|
|
71
|
+
appendGitignore(destDir);
|
|
72
|
+
mutateSpin.succeed("Veap configuration applied.");
|
|
73
|
+
// ---- 5. Workspace and storage folders.
|
|
74
|
+
for (const dir of [
|
|
75
|
+
path.join(destDir, "public"),
|
|
76
|
+
path.join(destDir, "storage"),
|
|
77
|
+
path.join(destDir, "plugins"),
|
|
78
|
+
path.join(destDir, "templates"),
|
|
79
|
+
]) {
|
|
80
|
+
if (!fs.existsSync(dir)) {
|
|
81
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ---- 6. Package manager: packageManager field, workspaces config,
|
|
85
|
+
// .yarnrc.yml for yarn berry.
|
|
86
|
+
applyPackageManager(destDir, pm);
|
|
87
|
+
// ---- 7. Docker configuration (optional).
|
|
88
|
+
if (withDocker) {
|
|
89
|
+
const { initDockerConfig } = await import("./docker.js");
|
|
90
|
+
await initDockerConfig(destDir, pm);
|
|
91
|
+
}
|
|
92
|
+
// ---- 8. Single dependency install, after all mutations.
|
|
93
|
+
if (!options?.skipInstall) {
|
|
94
|
+
const spinner = ora(`š¦ Installing dependencies (${pm} install)...`).start();
|
|
95
|
+
try {
|
|
96
|
+
pmInstall(pm, destDir);
|
|
97
|
+
spinner.succeed("Dependencies installed.");
|
|
98
|
+
}
|
|
99
|
+
catch (_err) {
|
|
100
|
+
spinner.fail(`Failed to install dependencies automatically. Run ${pm} install manually.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log(`\n⨠Veap project "${projectName}" initialized successfully!`);
|
|
104
|
+
console.log(`š” Go to project directory: cd ${projectName}`);
|
|
105
|
+
console.log(`š” Start dev server: ${pmRunCommand(pm, "dev")}`);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.error("Error initializing project:", err);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import cac from "cac";
|
|
3
|
+
import { initProject } from "./commands/init.js";
|
|
4
|
+
const cli = cac("create-veap");
|
|
5
|
+
cli
|
|
6
|
+
.command("[name]", "Initialize a new Veap project (asks for details when omitted)")
|
|
7
|
+
.option("--docker", "Initialize Docker configuration")
|
|
8
|
+
.option("--skip-install", "Skip dependencies installation")
|
|
9
|
+
.option("--pm <manager>", "Package manager to use: pnpm, npm, yarn or bun (auto-detected when omitted)")
|
|
10
|
+
.action(async (name, options) => {
|
|
11
|
+
await initProject(name, options);
|
|
12
|
+
});
|
|
13
|
+
cli.help();
|
|
14
|
+
cli.version("0.4.0");
|
|
15
|
+
cli.parse();
|
|
16
|
+
export { initProject };
|
package/dist/main.d.ts
ADDED
package/dist/main.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initProject } from "./commands/init.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Veap-specific dependencies layered on top of the freshly generated
|
|
3
|
+
* `create-next-app` project. The CNA-provided Next/React/TypeScript/Tailwind
|
|
4
|
+
* versions are intentionally NOT pinned here - we take whatever the official
|
|
5
|
+
* scaffolder ships, so generated apps never lag behind the framework.
|
|
6
|
+
*
|
|
7
|
+
* Only `@types/node` is overridden: the engine requirement is Node >= 22.
|
|
8
|
+
* The linter comes from CNA (`--eslint`); no Biome, no changesets.
|
|
9
|
+
*/
|
|
10
|
+
export interface VeapDependencyManifest {
|
|
11
|
+
dependencies: Record<string, string>;
|
|
12
|
+
devDependencies: Record<string, string>;
|
|
13
|
+
scripts?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Versions deliberately pinned OVER the CNA-provided ones: the engine
|
|
17
|
+
* requirement is Node >= 22. Applied after the spread merge so CNA cannot
|
|
18
|
+
* win these keys.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PINNED_DEV_DEPENDENCIES: Record<string, string>;
|
|
21
|
+
/** Full plugin-enabled project. */
|
|
22
|
+
export declare const FULL_MANIFEST: VeapDependencyManifest;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Veap-specific dependencies layered on top of the freshly generated
|
|
3
|
+
* `create-next-app` project. The CNA-provided Next/React/TypeScript/Tailwind
|
|
4
|
+
* versions are intentionally NOT pinned here - we take whatever the official
|
|
5
|
+
* scaffolder ships, so generated apps never lag behind the framework.
|
|
6
|
+
*
|
|
7
|
+
* Only `@types/node` is overridden: the engine requirement is Node >= 22.
|
|
8
|
+
* The linter comes from CNA (`--eslint`); no Biome, no changesets.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Versions deliberately pinned OVER the CNA-provided ones: the engine
|
|
12
|
+
* requirement is Node >= 22. Applied after the spread merge so CNA cannot
|
|
13
|
+
* win these keys.
|
|
14
|
+
*/
|
|
15
|
+
export const PINNED_DEV_DEPENDENCIES = {
|
|
16
|
+
"@types/node": "^22.20.2",
|
|
17
|
+
};
|
|
18
|
+
/** Full plugin-enabled project. */
|
|
19
|
+
export const FULL_MANIFEST = {
|
|
20
|
+
dependencies: {
|
|
21
|
+
dotenv: "^17.2.3",
|
|
22
|
+
pg: "^8.16.3",
|
|
23
|
+
"@veap/framework": "latest",
|
|
24
|
+
"@veap/ui": "latest",
|
|
25
|
+
},
|
|
26
|
+
devDependencies: {
|
|
27
|
+
...PINNED_DEV_DEPENDENCIES,
|
|
28
|
+
"@types/pg": "^8.16.0",
|
|
29
|
+
rimraf: "^6.1.3",
|
|
30
|
+
"tw-animate-css": "^1.4.0",
|
|
31
|
+
},
|
|
32
|
+
};
|
package/dist/mutate.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type VeapDependencyManifest } from "./manifests.js";
|
|
2
|
+
/**
|
|
3
|
+
* Post-scaffold mutations applied to the freshly generated
|
|
4
|
+
* `create-next-app` project. Everything CNA generated is kept as-is - these
|
|
5
|
+
* functions only inject what Veap needs on top of it.
|
|
6
|
+
*/
|
|
7
|
+
/** The dependency manifest applied to every generated project. */
|
|
8
|
+
export declare function getManifest(): VeapDependencyManifest;
|
|
9
|
+
/**
|
|
10
|
+
* Removes the CNA-generated `app/` directory before the overlay is applied.
|
|
11
|
+
* The overlay owns the whole `app/` tree: Next.js forbids a static page route
|
|
12
|
+
* at the same specificity as an optional catch-all, so a leftover
|
|
13
|
+
* `app/page.tsx` would conflict with the Veap `app/[[...catchAll]]/page.tsx`
|
|
14
|
+
* ("You cannot define a route with the same specificity as an optional
|
|
15
|
+
* catch-all route").
|
|
16
|
+
*/
|
|
17
|
+
export declare function clearAppDirectory(projectDir: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Merges the veap dependency manifest into the CNA-generated package.json.
|
|
20
|
+
* CNA-provided entries (next, react, typescript, tailwind, eslint setup)
|
|
21
|
+
* win for keys they already define; the manifest only fills the gaps and
|
|
22
|
+
* overrides `@types/node` (Node >= 22 engine) plus adds `engines`.
|
|
23
|
+
*
|
|
24
|
+
* `packageName` overrides the generated package name (CNA derives it from
|
|
25
|
+
* the folder, which loses the scope of names like `@scope/name`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function injectVeapDependencies(projectDir: string, packageName?: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Builds the starter `.env` content.
|
|
30
|
+
*
|
|
31
|
+
* ENCRYPTION_KEY is generated randomly per project (16 bytes, base64 - the
|
|
32
|
+
* exact contract validated at boot by @veap/framework, which has no fallback
|
|
33
|
+
* since the fail-fast fix). DATABASE_URL defaults to a local SQLite file;
|
|
34
|
+
* mail values are left as documented placeholders.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildEnvFile(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Writes the starter `.env`. CNA's generated `.gitignore` ignores `.env*`,
|
|
39
|
+
* so the file stays local to the new project.
|
|
40
|
+
*/
|
|
41
|
+
export declare function writeEnvFile(projectDir: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Appends the missing veap entries (storage dirs, plugin dist) to the
|
|
44
|
+
* CNA-generated `.gitignore`. Idempotent: no-op when everything is present.
|
|
45
|
+
*/
|
|
46
|
+
export declare function appendGitignore(projectDir: string): void;
|
package/dist/mutate.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { FULL_MANIFEST, PINNED_DEV_DEPENDENCIES, } from "./manifests.js";
|
|
5
|
+
/**
|
|
6
|
+
* Post-scaffold mutations applied to the freshly generated
|
|
7
|
+
* `create-next-app` project. Everything CNA generated is kept as-is - these
|
|
8
|
+
* functions only inject what Veap needs on top of it.
|
|
9
|
+
*/
|
|
10
|
+
/** The dependency manifest applied to every generated project. */
|
|
11
|
+
export function getManifest() {
|
|
12
|
+
return FULL_MANIFEST;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Removes the CNA-generated `app/` directory before the overlay is applied.
|
|
16
|
+
* The overlay owns the whole `app/` tree: Next.js forbids a static page route
|
|
17
|
+
* at the same specificity as an optional catch-all, so a leftover
|
|
18
|
+
* `app/page.tsx` would conflict with the Veap `app/[[...catchAll]]/page.tsx`
|
|
19
|
+
* ("You cannot define a route with the same specificity as an optional
|
|
20
|
+
* catch-all route").
|
|
21
|
+
*/
|
|
22
|
+
export function clearAppDirectory(projectDir) {
|
|
23
|
+
const appDir = path.join(projectDir, "app");
|
|
24
|
+
if (fs.existsSync(appDir)) {
|
|
25
|
+
fs.rmSync(appDir, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Merges the veap dependency manifest into the CNA-generated package.json.
|
|
30
|
+
* CNA-provided entries (next, react, typescript, tailwind, eslint setup)
|
|
31
|
+
* win for keys they already define; the manifest only fills the gaps and
|
|
32
|
+
* overrides `@types/node` (Node >= 22 engine) plus adds `engines`.
|
|
33
|
+
*
|
|
34
|
+
* `packageName` overrides the generated package name (CNA derives it from
|
|
35
|
+
* the folder, which loses the scope of names like `@scope/name`).
|
|
36
|
+
*/
|
|
37
|
+
export function injectVeapDependencies(projectDir, packageName) {
|
|
38
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
39
|
+
if (!fs.existsSync(pkgPath)) {
|
|
40
|
+
throw new Error(`package.json not found in ${projectDir}`);
|
|
41
|
+
}
|
|
42
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
43
|
+
const manifest = getManifest();
|
|
44
|
+
if (packageName) {
|
|
45
|
+
pkg.name = packageName;
|
|
46
|
+
}
|
|
47
|
+
pkg.dependencies = {
|
|
48
|
+
...manifest.dependencies,
|
|
49
|
+
...pkg.dependencies,
|
|
50
|
+
};
|
|
51
|
+
pkg.devDependencies = {
|
|
52
|
+
...manifest.devDependencies,
|
|
53
|
+
...pkg.devDependencies,
|
|
54
|
+
// Versions we own (node engine) win over CNA.
|
|
55
|
+
...PINNED_DEV_DEPENDENCIES,
|
|
56
|
+
};
|
|
57
|
+
if (manifest.scripts) {
|
|
58
|
+
pkg.scripts = { ...pkg.scripts, ...manifest.scripts };
|
|
59
|
+
}
|
|
60
|
+
pkg.engines = { ...pkg.engines, node: ">=22" };
|
|
61
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Builds the starter `.env` content.
|
|
65
|
+
*
|
|
66
|
+
* ENCRYPTION_KEY is generated randomly per project (16 bytes, base64 - the
|
|
67
|
+
* exact contract validated at boot by @veap/framework, which has no fallback
|
|
68
|
+
* since the fail-fast fix). DATABASE_URL defaults to a local SQLite file;
|
|
69
|
+
* mail values are left as documented placeholders.
|
|
70
|
+
*/
|
|
71
|
+
export function buildEnvFile() {
|
|
72
|
+
const encryptionKey = randomBytes(16).toString("base64");
|
|
73
|
+
return [
|
|
74
|
+
"# Veap application configuration (generated by create-veap)",
|
|
75
|
+
"",
|
|
76
|
+
"# 16-byte base64 key used for AES encryption at rest (recovery codes,",
|
|
77
|
+
"# TOTP secrets). Regenerate with: openssl rand -base64 16",
|
|
78
|
+
`ENCRYPTION_KEY="${encryptionKey}"`,
|
|
79
|
+
"",
|
|
80
|
+
"# Local SQLite database (swap for a PostgreSQL URL in production)",
|
|
81
|
+
'DATABASE_URL="sqlite:./storage/veap.sqlite"',
|
|
82
|
+
"",
|
|
83
|
+
"# Mail transport: smtp (default) | console | custom",
|
|
84
|
+
"MAIL_TRANSPORT=smtp",
|
|
85
|
+
"# MAIL_USERNAME=",
|
|
86
|
+
"# MAIL_PASSWORD=",
|
|
87
|
+
"# MAIL_FROM_ADDRESS=",
|
|
88
|
+
"",
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Writes the starter `.env`. CNA's generated `.gitignore` ignores `.env*`,
|
|
93
|
+
* so the file stays local to the new project.
|
|
94
|
+
*/
|
|
95
|
+
export function writeEnvFile(projectDir) {
|
|
96
|
+
const envPath = path.join(projectDir, ".env");
|
|
97
|
+
if (fs.existsSync(envPath)) {
|
|
98
|
+
// Never clobber: CNA does not generate .env, so this only triggers on
|
|
99
|
+
// re-runs over an existing directory.
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
fs.writeFileSync(envPath, buildEnvFile(), "utf-8");
|
|
103
|
+
}
|
|
104
|
+
/** Entries veap requires in `.gitignore` on top of CNA's output. */
|
|
105
|
+
const GITIGNORE_ENTRIES = ["public/storage/", "storage/", "**/*/dist"];
|
|
106
|
+
/**
|
|
107
|
+
* Appends the missing veap entries (storage dirs, plugin dist) to the
|
|
108
|
+
* CNA-generated `.gitignore`. Idempotent: no-op when everything is present.
|
|
109
|
+
*/
|
|
110
|
+
export function appendGitignore(projectDir) {
|
|
111
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
112
|
+
const existing = fs.existsSync(gitignorePath)
|
|
113
|
+
? fs.readFileSync(gitignorePath, "utf-8")
|
|
114
|
+
: "";
|
|
115
|
+
const present = new Set(existing.split("\n").map((line) => line.trim()));
|
|
116
|
+
const missing = GITIGNORE_ENTRIES.filter((entry) => !present.has(entry));
|
|
117
|
+
if (missing.length === 0)
|
|
118
|
+
return;
|
|
119
|
+
const block = [
|
|
120
|
+
"",
|
|
121
|
+
"# Veap additions (appended by create-veap)",
|
|
122
|
+
...missing,
|
|
123
|
+
"",
|
|
124
|
+
].join("\n");
|
|
125
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
126
|
+
fs.writeFileSync(gitignorePath, `${existing}${separator}${block}`, "utf-8");
|
|
127
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
|
|
2
|
+
export declare const PACKAGE_MANAGERS: PackageManager[];
|
|
3
|
+
export interface PackageManagerDetection {
|
|
4
|
+
pm: PackageManager;
|
|
5
|
+
/** Where the default came from: package.json field, a lockfile, or the framework default. */
|
|
6
|
+
source: "field" | "lockfile" | "default";
|
|
7
|
+
/** Human-readable detail, e.g. "pnpm@11.9.0" or "pnpm-lock.yaml". */
|
|
8
|
+
detail?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Detects the package manager to default to by inspecting `dir` (usually the
|
|
12
|
+
* directory the scaffolding command was launched from).
|
|
13
|
+
*
|
|
14
|
+
* Priority: `packageManager` field in package.json > lockfiles > default (pnpm).
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectPackageManager(dir: string): PackageManagerDetection;
|
|
17
|
+
/**
|
|
18
|
+
* Interactive numbered-choice prompt rendered on readline (no external
|
|
19
|
+
* prompt library, matching the rest of this CLI).
|
|
20
|
+
*
|
|
21
|
+
* The detected package manager is listed first as the default; pressing
|
|
22
|
+
* Enter accepts it. Accepts a number (1-4) or a name (`pnpm`, `npm`,
|
|
23
|
+
* `yarn`, `bun`). On EOF / closed stdin (piped input, CI) resolves to the
|
|
24
|
+
* detected default instead of hanging.
|
|
25
|
+
*/
|
|
26
|
+
export declare function promptPackageManager(detection: PackageManagerDetection): Promise<PackageManager>;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the package manager to use for a new project.
|
|
29
|
+
*
|
|
30
|
+
* Order: explicit `--pm <name>` flag (validated hard - scripts must not hang
|
|
31
|
+
* on a prompt) ā interactive prompt (TTY only) ā auto-detected default
|
|
32
|
+
* (non-TTY, CI).
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolvePackageManager(options?: {
|
|
35
|
+
pm?: string;
|
|
36
|
+
}): Promise<PackageManager>;
|
|
37
|
+
/**
|
|
38
|
+
* Applies the chosen package manager to the freshly scaffolded project:
|
|
39
|
+
*
|
|
40
|
+
* - pnpm: pins `packageManager` (corepack), writes `pnpm-workspace.yaml`
|
|
41
|
+
* only when missing (an existing one is the source of truth)
|
|
42
|
+
* - yarn: pins `packageManager`, adds `.yarnrc.yml` with `nodeLinker:
|
|
43
|
+
* node-modules` (berry's default PnP breaks Next.js)
|
|
44
|
+
* - npm/bun: removes `packageManager` (not managed by corepack), switches
|
|
45
|
+
* workspace definition to the `workspaces` field in package.json
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyPackageManager(projectDir: string, pm: PackageManager): void;
|
|
48
|
+
/** Command prefix for a package-manager-agnostic npm-script invocation. */
|
|
49
|
+
export declare function pmRunCommand(pm: PackageManager, script: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Installs dependencies with the chosen package manager (all four use the
|
|
52
|
+
* same `install` verb).
|
|
53
|
+
*/
|
|
54
|
+
export declare function pmInstall(pm: PackageManager, cwd: string, opts?: {
|
|
55
|
+
stdio?: "inherit" | "ignore" | "pipe";
|
|
56
|
+
}): void;
|