Skip to content

Commit 6061820

Browse files
committed
feat: add interactive prompts for project creation and Prettier setup; update version to 0.6.0
1 parent d074e57 commit 6061820

5 files changed

Lines changed: 220 additions & 31 deletions

File tree

npm/commands/create.js

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,40 @@ import fs from "fs/promises";
33
import path from "path";
44
import { handleSigint } from "../src/signal-handler.js";
55
import * as logger from "../src/logger.js";
6+
import { askInput, askConfirm } from "../src/prompt.js";
67

78
export default async function create(projectNameArg, options = {}) {
89
handleSigint();
910

1011
const cwd = process.cwd();
11-
const projectName = projectNameArg || "my-noshift-app";
12-
const usePrettier = options.prettier !== false; // default: true
12+
13+
// ── プロジェクト名 ──
14+
let projectName = projectNameArg;
15+
if (!projectName) {
16+
projectName = await askInput("Project name", "my-noshift-app");
17+
}
18+
19+
// ── Prettier ──
20+
// --no-prettier で明示的に無効化された場合はスキップ、
21+
// それ以外はユーザーに尋ねる
22+
let usePrettier;
23+
if (options.prettier === false) {
24+
usePrettier = false;
25+
} else {
26+
usePrettier = await askConfirm("Use Prettier?", true);
27+
}
1328

1429
const projectPath = path.join(cwd, projectName);
1530

1631
// Create project directory
17-
logger.step("Creating project directory...");
32+
logger.step("Creating project directory ...");
1833
await fs.mkdir(projectPath, { recursive: true });
1934
logger.dim(` ${projectPath}`);
2035

2136
process.chdir(projectPath);
2237

2338
// npm init
24-
logger.step("Initializing npm...");
39+
logger.step("Initializing npm ...");
2540
execSync("npm init -y", { stdio: "ignore" });
2641

2742
// Add scripts to package.json
@@ -30,6 +45,9 @@ export default async function create(projectNameArg, options = {}) {
3045
pkg.scripts = pkg.scripts ?? {};
3146
pkg.scripts.compile = "nsc compile";
3247
pkg.scripts.dev = "nsc dev";
48+
if (usePrettier) {
49+
pkg.scripts.format = "prettier --write ./src";
50+
}
3351
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
3452

3553
// Create nsjsconfig.json
@@ -53,32 +71,33 @@ export default async function create(projectNameArg, options = {}) {
5371
execSync("npm install --save-dev prettier prettier-plugin-noshift.js", {
5472
stdio: "ignore",
5573
});
56-
await fs.writeFile(".prettierignore", "dist/\nnode_modules/\n");
74+
75+
const prettierConfig = {
76+
semi: true,
77+
singleQuote: false,
78+
trailingComma: "es5",
79+
plugins: ["prettier-plugin-noshift.js"],
80+
};
5781
await fs.writeFile(
5882
".prettierrc",
59-
JSON.stringify(
60-
{
61-
semi: true,
62-
singleQuote: false,
63-
trailingComma: "es5",
64-
plugins: ["prettier-plugin-noshift.js"],
65-
},
66-
null,
67-
2,
68-
) + "\n",
83+
JSON.stringify(prettierConfig, null, 2) + "\n",
6984
);
85+
logger.success("Created .prettierrc");
86+
87+
await fs.writeFile(".prettierignore", "dist/\nnode_modules/\n");
88+
logger.success("Created .prettierignore");
7089
}
7190

7291
// Install noshift.js
73-
logger.step("Installing noshift.js...");
92+
logger.step("Installing noshift.js ...");
7493
execSync("npm install noshift.js", { stdio: "ignore" });
7594

7695
// Create project files
77-
logger.step("Creating project files...");
96+
logger.step("Creating project files ...");
7897
await fs.mkdir("src", { recursive: true });
7998
await fs.writeFile(
8099
"src/index.nsjs",
81-
"console.log^8^2^3hello, ^3world!^2^9;\n",
100+
"console.log^8^2^3hello, ^3world^1^2^9;\n",
82101
);
83102

84103
// .gitignore

npm/commands/init.js

Lines changed: 134 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { writeFile, access } from "fs/promises";
1+
import { writeFile, readFile, access } from "fs/promises";
22
import path from "path";
33
import { handleSigint } from "../src/signal-handler.js";
44
import * as logger from "../src/logger.js";
5+
import { askConfirm } from "../src/prompt.js";
56

67
const DEFAULT_CONFIG = {
78
compileroptions: {
@@ -12,29 +13,152 @@ const DEFAULT_CONFIG = {
1213
},
1314
};
1415

16+
/** .prettierrc 系のファイル名(優先順) */
17+
const PRETTIERRC_FILES = [
18+
".prettierrc",
19+
".prettierrc.json",
20+
".prettierrc.yml",
21+
".prettierrc.yaml",
22+
".prettierrc.json5",
23+
".prettierrc.cjs",
24+
".prettierrc.mjs",
25+
"prettier.config.js",
26+
"prettier.config.cjs",
27+
"prettier.config.mjs",
28+
];
29+
30+
const PLUGIN_NAME = "prettier-plugin-noshift.js";
31+
32+
/**
33+
* 既存の .prettierrc / .prettierrc.json を読み込み plugins に追加する。
34+
* JSON 形式のみ自動編集可能。それ以外は手動追加を案内する。
35+
* @param {string} filePath
36+
*/
37+
async function addPluginToExistingConfig(filePath) {
38+
const basename = path.basename(filePath);
39+
const isJson =
40+
basename === ".prettierrc" ||
41+
basename === ".prettierrc.json";
42+
43+
if (!isJson) {
44+
logger.warn(
45+
`Found ${basename} — please add "${PLUGIN_NAME}" to the plugins array manually.`,
46+
);
47+
return;
48+
}
49+
50+
try {
51+
const raw = await readFile(filePath, "utf-8");
52+
const config = JSON.parse(raw);
53+
const plugins = Array.isArray(config.plugins) ? config.plugins : [];
54+
55+
if (plugins.includes(PLUGIN_NAME)) {
56+
logger.info(`${basename} already contains "${PLUGIN_NAME}".`);
57+
return;
58+
}
59+
60+
plugins.push(PLUGIN_NAME);
61+
config.plugins = plugins;
62+
await writeFile(filePath, JSON.stringify(config, null, 2) + "\n");
63+
logger.success(`Added "${PLUGIN_NAME}" to ${basename}`);
64+
} catch (err) {
65+
logger.error(`Failed to update ${basename}: ${err.message}`);
66+
logger.warn(`Please add "${PLUGIN_NAME}" to the plugins array manually.`);
67+
}
68+
}
69+
70+
/**
71+
* 新しい .prettierrc を作成する
72+
*/
73+
async function createPrettierConfig() {
74+
const prettierConfig = {
75+
semi: true,
76+
singleQuote: false,
77+
trailingComma: "es5",
78+
plugins: [PLUGIN_NAME],
79+
};
80+
await writeFile(".prettierrc", JSON.stringify(prettierConfig, null, 2) + "\n");
81+
logger.success("Created .prettierrc");
82+
}
83+
1584
export default async function init() {
1685
handleSigint();
1786

18-
const configPath = path.join(process.cwd(), "nsjsconfig.json");
87+
const cwd = process.cwd();
88+
const configPath = path.join(cwd, "nsjsconfig.json");
1989

90+
// ── nsjsconfig.json ──
91+
let configExists = false;
2092
try {
2193
await access(configPath);
22-
logger.errorCode(
23-
"NS4",
24-
"nsjsconfig.json already exists in the current directory.",
25-
);
26-
process.exit(1);
94+
configExists = true;
2795
} catch {
28-
// ファイルが存在しないのが正常
96+
// not found — OK
97+
}
98+
99+
if (configExists) {
100+
logger.warn("nsjsconfig.json already exists in the current directory.");
101+
const overwrite = await askConfirm("Overwrite?", false);
102+
if (!overwrite) {
103+
logger.info("Skipped nsjsconfig.json");
104+
} else {
105+
await writeFile(
106+
configPath,
107+
JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n",
108+
);
109+
logger.success("Overwritten nsjsconfig.json");
110+
}
111+
} else {
112+
await writeFile(
113+
configPath,
114+
JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n",
115+
);
116+
logger.success("Created nsjsconfig.json");
29117
}
30118

31-
await writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
32-
logger.success("Created nsjsconfig.json");
33119
logger.dim(
34120
` compileroptions.rootdir : ${DEFAULT_CONFIG.compileroptions.rootdir}`,
35121
);
36122
logger.dim(
37123
` compileroptions.outdir : ${DEFAULT_CONFIG.compileroptions.outdir}`,
38124
);
125+
126+
// ── Prettier ──
127+
const usePrettier = await askConfirm("Set up Prettier?", true);
128+
129+
if (usePrettier) {
130+
// 既存の prettierrc 系ファイルを探す
131+
let existingFile = null;
132+
for (const name of PRETTIERRC_FILES) {
133+
try {
134+
await access(path.join(cwd, name));
135+
existingFile = name;
136+
break;
137+
} catch {
138+
// not found — continue
139+
}
140+
}
141+
142+
if (existingFile) {
143+
await addPluginToExistingConfig(path.join(cwd, existingFile));
144+
} else {
145+
await createPrettierConfig();
146+
}
147+
148+
// .prettierignore
149+
const ignorePath = path.join(cwd, ".prettierignore");
150+
let ignoreExists = false;
151+
try {
152+
await access(ignorePath);
153+
ignoreExists = true;
154+
} catch {
155+
// not found
156+
}
157+
if (!ignoreExists) {
158+
await writeFile(ignorePath, "dist/\nnode_modules/\n");
159+
logger.success("Created .prettierignore");
160+
}
161+
}
162+
39163
console.log("");
40164
}

npm/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

npm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "noshift.js",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"description": "Joke language.",
55
"bin": {
66
"nsc": "./bin/cli.js"

npm/src/prompt.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* 対話式プロンプトユーティリティ
3+
*/
4+
import readline from "readline/promises";
5+
6+
/**
7+
* ユーザーにテキスト入力を求める
8+
* @param {string} question
9+
* @param {string} [defaultValue]
10+
* @returns {Promise<string>}
11+
*/
12+
export async function askInput(question, defaultValue) {
13+
const rl = readline.createInterface({
14+
input: process.stdin,
15+
output: process.stdout,
16+
});
17+
const suffix = defaultValue ? ` (${defaultValue})` : "";
18+
try {
19+
const answer = await rl.question(`${question}${suffix}: `);
20+
return answer.trim() || defaultValue || "";
21+
} finally {
22+
rl.close();
23+
}
24+
}
25+
26+
/**
27+
* ユーザーに Yes/No を尋ねる
28+
* @param {string} question
29+
* @param {boolean} [defaultYes=true]
30+
* @returns {Promise<boolean>}
31+
*/
32+
export async function askConfirm(question, defaultYes = true) {
33+
const rl = readline.createInterface({
34+
input: process.stdin,
35+
output: process.stdout,
36+
});
37+
const hint = defaultYes ? "Y/n" : "y/N";
38+
try {
39+
const answer = await rl.question(`${question} (${hint}): `);
40+
const trimmed = answer.trim().toLowerCase();
41+
if (trimmed === "") return defaultYes;
42+
return trimmed === "y" || trimmed === "yes";
43+
} finally {
44+
rl.close();
45+
}
46+
}

0 commit comments

Comments
 (0)