Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/api-routes/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-api-routes",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-basic",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/complex-routing/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-complex-routing",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/custom-ws-transport/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-custom-ws-transport",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/mpa/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-mpa",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/plugin-authoring/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-plugin-authoring",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/with-sqlite/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-with-sqlite",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/with-tailwind/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-with-tailwind",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions examples/with-trpc/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "example-with-trpc",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
4 changes: 3 additions & 1 deletion packages/create-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { Command } from "commander";
import { derivePackageName } from "./project-name.js";
import pc from "picocolors";
import prompts from "prompts";

Expand Down Expand Up @@ -67,6 +68,7 @@ program
const projectName = response.projectName || name;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Trimming the projectName ensures that leading or trailing whitespace from user input doesn't result in unexpected directory names or invalid package metadata. This improves the robustness of the scaffolding process.

Suggested change
const projectName = response.projectName || name;
const projectName = (response.projectName || name).trim();

const template = response.template || options.template;
const targetDir = path.resolve(process.cwd(), projectName);
const packageName = derivePackageName(projectName, targetDir);

if (fs.existsSync(targetDir)) {
console.error(pc.red(`✖ Directory ${projectName} already exists!`));
Expand Down Expand Up @@ -101,7 +103,7 @@ program
const pkgPath = path.join(targetDir, "package.json");
if (fs.existsSync(pkgPath)) {
const projPkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
projPkg.name = projectName;
projPkg.name = packageName;
delete projPkg.private; // Templates shouldn't be private by default

const updateDeps = (deps: Record<string, string> | undefined) => {
Expand Down
13 changes: 13 additions & 0 deletions packages/create-app/src/project-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import path from "node:path";

const scopedPackagePattern = /^@[^/\\]+[/\\][^/\\]+$/;

export function derivePackageName(inputName: string, targetDir: string): string {
const trimmedName = inputName.trim();

if (scopedPackagePattern.test(trimmedName)) {
return trimmedName.replace("\\", "/");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

NPM package names must be lowercase. Lowercasing the scoped name here ensures that the generated package.json adheres to NPM naming requirements, even if the user provided uppercase characters in the input.

Suggested change
return trimmedName.replace("\\", "/");
return trimmedName.replace("\\", "/").toLowerCase();

}

return path.basename(path.resolve(targetDir));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to scoped names, the basename of the target directory should be lowercased to ensure a valid NPM package name. This prevents issues where a directory named with uppercase letters would otherwise produce an invalid package.json.

Suggested change
return path.basename(path.resolve(targetDir));
return path.basename(path.resolve(targetDir)).toLowerCase();

}
16 changes: 16 additions & 0 deletions packages/create-app/tests/scaffold.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { derivePackageName } from "../src/project-name.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const templatesDir = path.resolve(__dirname, "../templates");
Expand Down Expand Up @@ -96,6 +97,21 @@ describe("create-app scaffolding", () => {
}
});

it("derives a valid package name from path-like project names", () => {
expect(derivePackageName("my-real-app", "/tmp/my-real-app")).toBe(
"my-real-app",
);
expect(derivePackageName("/tmp/my-real-app", "/tmp/my-real-app")).toBe(
"my-real-app",
);
expect(derivePackageName("apps/my-real-app", "/tmp/apps/my-real-app")).toBe(
"my-real-app",
);
expect(
derivePackageName("@scope/my-real-app", "/tmp/@scope/my-real-app"),
).toBe("@scope/my-real-app");
});

it("copy filter excludes node_modules, dist, and .turbo", async () => {
// Test the filter function logic used in create-app
const filter = (src: string) => {
Expand Down
Loading