vue-art-e4n 0.0.1

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.
@@ -0,0 +1,35 @@
1
+ import { EXCLUSIVE_RULES, STORE_ARCHITECTURE_RULES } from "../config/rules.js";
2
+
3
+ /**
4
+ * Disables feature choices based on exclusive selection rules.
5
+ *
6
+ * @param {string[]} selectedFeatures - Features currently selected by the user.
7
+ * @param {Array<{ value: string, label: string, disabled?: string }>} featureChoices - All available feature choices.
8
+ * @returns {Array<{ value: string, label: string, disabled?: string }>} Updated feature choices with disabled reasons applied.
9
+ */
10
+ export function applyExclusiveRules(selectedFeatures, featureChoices) {
11
+ return featureChoices.map((choice) => {
12
+ const copy = { ...choice };
13
+ EXCLUSIVE_RULES.forEach((rule) => {
14
+ if (
15
+ selectedFeatures.includes(rule.ifSelected) &&
16
+ rule.disable === choice.value
17
+ ) {
18
+ copy.disabled = rule.reason;
19
+ }
20
+ });
21
+ return copy;
22
+ });
23
+ }
24
+ /**
25
+ * Filters architectures that are forbidden for a specific store.
26
+ *
27
+ * @param {string} store - The selected store type.
28
+ * @param {string[]} architectures - List of available architectures.
29
+ * @returns {string[]} Filtered architectures allowed for the store.
30
+ */
31
+ export function filterArchitecturesByStore(store, architectures) {
32
+ const rule = STORE_ARCHITECTURE_RULES.find((r) => r.store === store);
33
+ if (!rule) return architectures;
34
+ return architectures.filter((a) => !rule.forbiddenArchitectures.includes(a));
35
+ }
@@ -0,0 +1,92 @@
1
+ import { execa } from "execa";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { createPackageJson } from "./createPackageJson.js";
5
+ /**
6
+ * Installs npm dependencies using selected package manager.
7
+ * Handles Ctrl+C interruption gracefully.
8
+ *
9
+ * @param {Object} options
10
+ * @param {string} options.projectDir
11
+ * @param {"npm"|"yarn"|"pnpm"} options.packageManager
12
+ * @param {string[]} options.features
13
+ * @param {string} options.framework
14
+ *
15
+ * @returns {Promise<void>}
16
+ */
17
+ export async function installPackages({
18
+ projectDir,
19
+ framework,
20
+ features,
21
+ packageManager,
22
+ projectName,
23
+ }) {
24
+ const pkgPath = path.join(projectDir, "package.json");
25
+ if (!fs.existsSync(pkgPath)) {
26
+ const { CORE_PACKAGES, PACKAGE_MAP } = await import(
27
+ "../config/packages.js"
28
+ );
29
+ const packages = new Set();
30
+ CORE_PACKAGES[framework]?.forEach((p) => packages.add(p));
31
+ features.forEach((f) => {
32
+ const pkgList = PACKAGE_MAP[framework]?.[f];
33
+ if (pkgList) pkgList.forEach((p) => packages.add(p));
34
+ });
35
+ createPackageJson({
36
+ projectDir,
37
+ projectName,
38
+ framework,
39
+ features,
40
+ packages: [...packages],
41
+ });
42
+ console.log("📝 Created package.json");
43
+ }
44
+
45
+ const pkgContent = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
46
+ const hasDependencies =
47
+ (pkgContent.dependencies &&
48
+ Object.keys(pkgContent.dependencies).length > 0) ||
49
+ (pkgContent.devDependencies &&
50
+ Object.keys(pkgContent.devDependencies).length > 0);
51
+
52
+ if (!hasDependencies) {
53
+ console.log("No packages to install.");
54
+ return;
55
+ }
56
+
57
+ console.log(`\n📦 Installing packages with ${packageManager}...\n`);
58
+
59
+ let args = [];
60
+ if (packageManager === "npm") args = ["install"];
61
+ if (packageManager === "yarn") args = ["install"];
62
+ if (packageManager === "pnpm") args = ["install"];
63
+
64
+ try {
65
+ const subprocess = execa(packageManager, args, {
66
+ cwd: projectDir,
67
+ stdio: "inherit",
68
+ });
69
+
70
+ await subprocess;
71
+ console.log("\n✅ Packages installed successfully!\n");
72
+ } catch (error) {
73
+ if (
74
+ error.signal === "SIGINT" ||
75
+ error.code === "SIGINT" ||
76
+ error.exitCode === 130 ||
77
+ error.code === 130 ||
78
+ error.message?.includes("SIGINT") ||
79
+ error.message?.includes("canceled") ||
80
+ error.message?.includes("User force closed")
81
+ ) {
82
+ const sigintError = new Error(
83
+ "Installation interrupted by user (SIGINT)"
84
+ );
85
+ sigintError.signal = "SIGINT";
86
+ sigintError.code = "SIGINT";
87
+ sigintError.exitCode = 130;
88
+ throw sigintError;
89
+ }
90
+ throw error;
91
+ }
92
+ }
package/core/runCLI.js ADDED
@@ -0,0 +1,67 @@
1
+ import chalk from "chalk";
2
+ import path from "path";
3
+ import { collectProjectConfig } from "./steps/collectProjectConfig.js";
4
+ import { reviewAndConfirm } from "./steps/reviewAndConfirm.js";
5
+ import { createProjectStructure } from "./createProjectStructure.js";
6
+ import { createPackageJson } from "./createPackageJson.js";
7
+ import { installPackages } from "./installPackages.js";
8
+
9
+ /**
10
+ * Main CLI workflow controller.
11
+ *
12
+ * Responsibilities:
13
+ * - Collect user configuration
14
+ * - Allow review & editing
15
+ * - Generate project structure
16
+ * - Generate package.json
17
+ * - Optionally install dependencies
18
+ *
19
+ * @returns {Promise<void>}
20
+ */
21
+ export async function runCLI() {
22
+ try {
23
+ const config = await collectProjectConfig();
24
+
25
+ const confirmed = await reviewAndConfirm(config);
26
+ if (!confirmed) {
27
+ console.log(chalk.yellow("Project creation canceled."));
28
+ return;
29
+ }
30
+
31
+ const projectDir = path.resolve(process.cwd(), config.projectName);
32
+
33
+ createProjectStructure(config);
34
+
35
+ createPackageJson({
36
+ projectDir,
37
+ ...config,
38
+ });
39
+
40
+ console.log("📝 Created package.json");
41
+
42
+ if (confirmed.installNodeModules) {
43
+ await installPackages({
44
+ projectDir,
45
+ ...config,
46
+ });
47
+ }
48
+
49
+ console.log(chalk.green("\n✨ Project created successfully!\n"));
50
+ const pm = confirmed.packageManager || "npm";
51
+ const pmCommand = pm === "yarn" ? "yarn" : pm === "pnpm" ? "pnpm" : "npm";
52
+
53
+ console.log(chalk.cyan("📋 Next steps:\n"));
54
+ console.log(chalk.cyan(` cd ${confirmed.projectName}`));
55
+ if (!confirmed.installNodeModules) {
56
+ console.log(chalk.yellow(` ${pmCommand} install`));
57
+ }
58
+ console.log(chalk.cyan(` ${pmCommand} run dev\n`));
59
+ } catch (error) {
60
+ if (error?.name === "ExitPromptError") {
61
+ console.log(chalk.yellow("\n⛔ Operation canceled.\n"));
62
+ process.exit(0);
63
+ }
64
+
65
+ throw error;
66
+ }
67
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Prompts the user to select a project architecture.
3
+ * Filters available architectures based on store and recommends an optimal choice.
4
+ *
5
+ * @param {Object} options
6
+ * @param {"vue"|"vuetify"|"nuxt"} options.framework - Selected framework.
7
+ * @param {string} options.scale - Project scale (e.g., small, medium, large).
8
+ * @param {string} options.lifetime - Expected project lifetime.
9
+ * @param {string[]} options.features - Selected project features.
10
+ * @param {Record<string, string[]>} options.ARCHITECTURES - Available architectures per framework.
11
+ * @param {function} options.filterArchitecturesByStore - Function to filter architectures by store.
12
+ * @param {function} options.getRecommendedArchitecture - Function returning recommended architecture.
13
+ * @param {function} options.confirmPrompt - Async function to prompt user for yes/no confirmation.
14
+ * @param {function} options.singleChoiceCheckbox - Async function to prompt user to select a single option.
15
+ * @param {boolean} [options.skipRecommendation=false] - If true, skip recommendation and show list directly.
16
+ * @param {string} [options.currentArchitecture] - Current architecture value (for edit mode).
17
+ * @returns {Promise<string>} Selected or recommended architecture.
18
+ */
19
+ export async function selectArchitecture({
20
+ framework,
21
+ scale,
22
+ lifetime,
23
+ features,
24
+ ARCHITECTURES,
25
+ filterArchitecturesByStore,
26
+ getRecommendedArchitecture,
27
+ confirmPrompt,
28
+ singleChoiceCheckbox,
29
+ skipRecommendation = false,
30
+ currentArchitecture,
31
+ }) {
32
+ let architectures = ARCHITECTURES[framework];
33
+
34
+ if (!architectures || architectures.length === 0) {
35
+ throw new Error(
36
+ `No architectures defined for framework: ${framework}. Available frameworks: ${Object.keys(ARCHITECTURES).join(", ")}`
37
+ );
38
+ }
39
+
40
+ architectures = filterArchitecturesByStore(
41
+ features.includes("pinia")
42
+ ? "pinia"
43
+ : features.includes("vuex")
44
+ ? "vuex"
45
+ : null,
46
+ architectures
47
+ );
48
+
49
+ if (!architectures || architectures.length === 0) {
50
+ throw new Error(
51
+ `No architectures available after filtering for framework: ${framework}`
52
+ );
53
+ }
54
+
55
+ if (skipRecommendation) {
56
+ return singleChoiceCheckbox({
57
+ name: "architecture",
58
+ message: "Select architecture:",
59
+ choices: architectures.map((a) => ({ name: a, value: a })),
60
+ defaultValue: currentArchitecture && architectures.includes(currentArchitecture)
61
+ ? currentArchitecture
62
+ : undefined,
63
+ });
64
+ }
65
+
66
+ const recommended = getRecommendedArchitecture({
67
+ framework,
68
+ scale,
69
+ lifetime,
70
+ features,
71
+ });
72
+
73
+ const isRecommendedAvailable = architectures.includes(recommended);
74
+
75
+ if (isRecommendedAvailable) {
76
+ const useRecommended = await confirmPrompt(
77
+ `Recommended architecture: "${recommended}". Use it?`,
78
+ true
79
+ );
80
+
81
+ if (useRecommended) return recommended;
82
+ }
83
+
84
+ return singleChoiceCheckbox({
85
+ name: "architecture",
86
+ message: "Select architecture:",
87
+ choices: architectures.map((a) => ({ name: a, value: a })),
88
+ });
89
+ }
@@ -0,0 +1,103 @@
1
+ import { singleChoiceCheckbox, confirmPrompt } from "../../helpers/prompts.js";
2
+ import { FEATURE_CHOICES } from "../../config/featuresConfig.js";
3
+ import {
4
+ ARCHITECTURES,
5
+ getRecommendedArchitecture,
6
+ } from "../../config/architectures.js";
7
+ import {
8
+ applyExclusiveRules,
9
+ filterArchitecturesByStore,
10
+ } from "../featureRules.js";
11
+ import { selectArchitecture } from "../selectArchitecture.js";
12
+ import inquirer from "inquirer";
13
+
14
+ export async function collectProjectConfig() {
15
+ const { projectName } = await inquirer.prompt([
16
+ {
17
+ type: "input",
18
+ name: "projectName",
19
+ message: "Project name:",
20
+ validate: (v) => (v && !v.includes(" ") ? true : "Invalid project name"),
21
+ },
22
+ ]);
23
+
24
+ const framework = await singleChoiceCheckbox({
25
+ name: "framework",
26
+ message: "Select project type:",
27
+ choices: [
28
+ { name: "Vue", value: "vue" },
29
+ { name: "Nuxt 3", value: "nuxt" },
30
+ { name: "Vue 3 + Vuetify", value: "vuetify" },
31
+ ],
32
+ });
33
+
34
+ let features = [];
35
+ const featureChoices = applyExclusiveRules(features, FEATURE_CHOICES);
36
+ const featureResult = await inquirer.prompt([
37
+ {
38
+ type: "checkbox",
39
+ name: "features",
40
+ message: "Select features:",
41
+ choices: featureChoices,
42
+ },
43
+ ]);
44
+ features = featureResult.features;
45
+
46
+ const store = await singleChoiceCheckbox({
47
+ name: "store",
48
+ message: "Select store:",
49
+ choices: [
50
+ { name: "None", value: null },
51
+ { name: "Pinia", value: "pinia" },
52
+ { name: "Vuex", value: "vuex" },
53
+ ],
54
+ });
55
+ if (store) features.push(store);
56
+
57
+ const scale = await singleChoiceCheckbox({
58
+ name: "scale",
59
+ message: "Project scale:",
60
+ choices: [
61
+ { name: "Small", value: "small" },
62
+ { name: "Medium", value: "medium" },
63
+ { name: "Large", value: "large" },
64
+ ],
65
+ });
66
+
67
+ const lifetime = await singleChoiceCheckbox({
68
+ name: "lifetime",
69
+ message: "Project lifetime:",
70
+ choices: [
71
+ { name: "Short-term", value: "short" },
72
+ { name: "Long-term", value: "long" },
73
+ ],
74
+ });
75
+
76
+ const architecture = await selectArchitecture({
77
+ framework,
78
+ scale,
79
+ lifetime,
80
+ features,
81
+ ARCHITECTURES,
82
+ filterArchitecturesByStore,
83
+ getRecommendedArchitecture,
84
+ confirmPrompt,
85
+ singleChoiceCheckbox,
86
+ });
87
+
88
+ const packageManager = await singleChoiceCheckbox({
89
+ name: "packageManager",
90
+ message: "Select package manager:",
91
+ choices: ["npm", "yarn", "pnpm"].map((v) => ({ name: v, value: v })),
92
+ });
93
+
94
+ return {
95
+ projectName,
96
+ framework,
97
+ features,
98
+ architecture,
99
+ scale,
100
+ lifetime,
101
+ packageManager,
102
+ };
103
+ }
@@ -0,0 +1,24 @@
1
+ import path from "path";
2
+ import chalk from "chalk";
3
+ import { createProjectStructure } from "../createProjectStructure.js";
4
+ import { createPackageJson } from "../createPackageJson.js";
5
+ import { installPackages } from "../installPackages.js";
6
+ import { getPackagesList } from "../utils/getPackagesList.js";
7
+
8
+ export async function finalizeProject(config) {
9
+ const projectDir = path.resolve(process.cwd(), config.projectName);
10
+ const packages = getPackagesList(config.framework, config.features);
11
+
12
+ createProjectStructure(config);
13
+ createPackageJson({ ...config, projectDir, packages });
14
+
15
+ const install = await import("../../helpers/prompts.js").then((m) =>
16
+ m.confirmPrompt("Install node_modules now?", true)
17
+ );
18
+
19
+ if (install) {
20
+ await installPackages({ ...config, projectDir });
21
+ }
22
+
23
+ console.log(chalk.green("\n✨ Project created successfully!\n"));
24
+ }
@@ -0,0 +1,242 @@
1
+ import { confirmPrompt, singleChoiceCheckbox } from "../../helpers/prompts.js";
2
+ import { getPackagesList } from "../utils/getPackagesList.js";
3
+ import inquirer from "inquirer";
4
+ import { FEATURE_CHOICES } from "../../config/featuresConfig.js";
5
+ import {
6
+ ARCHITECTURES,
7
+ getRecommendedArchitecture,
8
+ } from "../../config/architectures.js";
9
+ import {
10
+ applyExclusiveRules,
11
+ filterArchitecturesByStore,
12
+ } from "../featureRules.js";
13
+ import { selectArchitecture } from "../selectArchitecture.js";
14
+
15
+ /**
16
+ * Allows user to edit individual configuration options
17
+ */
18
+ async function editConfig(config) {
19
+ let editedConfig = { ...config };
20
+
21
+ // Edit project name
22
+ if (
23
+ await confirmPrompt(
24
+ `Edit project name? (Current: ${config.projectName})`,
25
+ false
26
+ )
27
+ ) {
28
+ const { projectName } = await inquirer.prompt([
29
+ {
30
+ type: "input",
31
+ name: "projectName",
32
+ message: "Project name:",
33
+ default: config.projectName,
34
+ validate: (v) =>
35
+ v && !v.includes(" ") ? true : "Invalid project name",
36
+ },
37
+ ]);
38
+ editedConfig.projectName = projectName;
39
+ }
40
+
41
+ // Edit framework
42
+ if (
43
+ await confirmPrompt(`Edit framework? (Current: ${config.framework})`, false)
44
+ ) {
45
+ editedConfig.framework = await singleChoiceCheckbox({
46
+ name: "framework",
47
+ message: "Select project type:",
48
+ choices: [
49
+ { name: "Vue", value: "vue" },
50
+ { name: "Nuxt 3", value: "nuxt" },
51
+ { name: "Vue 3 + Vuetify", value: "vuetify" },
52
+ ],
53
+ defaultValue: config.framework,
54
+ });
55
+ }
56
+
57
+ // Edit features
58
+ const currentStore = config.features.find(
59
+ (f) => f === "pinia" || f === "vuex"
60
+ );
61
+ const featuresWithoutStore = config.features.filter(
62
+ (f) => f !== "pinia" && f !== "vuex"
63
+ );
64
+
65
+ if (
66
+ await confirmPrompt(
67
+ `Edit features? (Current: ${config.features.join(", ") || "none"})`,
68
+ false
69
+ )
70
+ ) {
71
+ const featureChoices = applyExclusiveRules(
72
+ featuresWithoutStore,
73
+ FEATURE_CHOICES
74
+ );
75
+ const featureResult = await inquirer.prompt([
76
+ {
77
+ type: "checkbox",
78
+ name: "features",
79
+ message: "Select features:",
80
+ choices: featureChoices,
81
+ default: featuresWithoutStore,
82
+ },
83
+ ]);
84
+ editedConfig.features = featureResult.features;
85
+ } else {
86
+ // Keep features as is (without store, we'll add it separately)
87
+ editedConfig.features = [...featuresWithoutStore];
88
+ }
89
+
90
+ // Edit store (separate question)
91
+ if (
92
+ await confirmPrompt(
93
+ `Edit store? (Current: ${currentStore || "None"})`,
94
+ false
95
+ )
96
+ ) {
97
+ const store = await singleChoiceCheckbox({
98
+ name: "store",
99
+ message: "Select store:",
100
+ choices: [
101
+ { name: "None", value: null },
102
+ { name: "Pinia", value: "pinia" },
103
+ { name: "Vuex", value: "vuex" },
104
+ ],
105
+ defaultValue: currentStore || null,
106
+ });
107
+ if (store) editedConfig.features.push(store);
108
+ } else if (currentStore) {
109
+ // Keep old store if not editing
110
+ editedConfig.features.push(currentStore);
111
+ }
112
+
113
+ // Edit scale
114
+ if (
115
+ await confirmPrompt(`Edit project scale? (Current: ${config.scale})`, false)
116
+ ) {
117
+ editedConfig.scale = await singleChoiceCheckbox({
118
+ name: "scale",
119
+ message: "Project scale:",
120
+ choices: [
121
+ { name: "Small", value: "small" },
122
+ { name: "Medium", value: "medium" },
123
+ { name: "Large", value: "large" },
124
+ ],
125
+ defaultValue: config.scale,
126
+ });
127
+ }
128
+
129
+ // Edit lifetime
130
+ if (
131
+ await confirmPrompt(
132
+ `Edit project lifetime? (Current: ${config.lifetime})`,
133
+ false
134
+ )
135
+ ) {
136
+ editedConfig.lifetime = await singleChoiceCheckbox({
137
+ name: "lifetime",
138
+ message: "Project lifetime:",
139
+ choices: [
140
+ { name: "Short-term", value: "short" },
141
+ { name: "Long-term", value: "long" },
142
+ ],
143
+ defaultValue: config.lifetime,
144
+ });
145
+ }
146
+
147
+ // Edit architecture
148
+ if (
149
+ await confirmPrompt(
150
+ `Edit architecture? (Current: ${editedConfig.architecture})`,
151
+ false
152
+ )
153
+ ) {
154
+ editedConfig.architecture = await selectArchitecture({
155
+ framework: editedConfig.framework,
156
+ scale: editedConfig.scale,
157
+ lifetime: editedConfig.lifetime,
158
+ features: editedConfig.features,
159
+ ARCHITECTURES,
160
+ filterArchitecturesByStore,
161
+ getRecommendedArchitecture,
162
+ confirmPrompt,
163
+ singleChoiceCheckbox,
164
+ skipRecommendation: true,
165
+ currentArchitecture: editedConfig.architecture,
166
+ });
167
+ }
168
+
169
+ // Edit package manager
170
+ if (
171
+ await confirmPrompt(
172
+ `Edit package manager? (Current: ${config.packageManager})`,
173
+ false
174
+ )
175
+ ) {
176
+ editedConfig.packageManager = await singleChoiceCheckbox({
177
+ name: "packageManager",
178
+ message: "Select package manager:",
179
+ choices: ["npm", "yarn", "pnpm"].map((v) => ({ name: v, value: v })),
180
+ defaultValue: config.packageManager,
181
+ });
182
+ }
183
+
184
+ return editedConfig;
185
+ }
186
+
187
+ export async function reviewAndConfirm(config) {
188
+ const packages = getPackagesList(config.framework, config.features);
189
+
190
+ // Ask if user wants to edit
191
+ const wantsToEdit = await confirmPrompt(
192
+ `
193
+ Project: ${config.projectName}
194
+ Framework: ${config.framework}
195
+ Architecture: ${config.architecture}
196
+ Features: ${config.features.join(", ") || "none"}
197
+ Packages: ${packages.join(", ") || "none"}
198
+
199
+ Do you want to edit any of these options?`,
200
+ false
201
+ );
202
+
203
+ let finalConfig = config;
204
+ if (wantsToEdit) {
205
+ finalConfig = await editConfig(config);
206
+ // Show updated config and ask again
207
+ const updatedPackages = getPackagesList(
208
+ finalConfig.framework,
209
+ finalConfig.features
210
+ );
211
+ const proceed = await confirmPrompt(
212
+ `
213
+ Updated configuration:
214
+ Project: ${finalConfig.projectName}
215
+ Framework: ${finalConfig.framework}
216
+ Architecture: ${finalConfig.architecture}
217
+ Features: ${finalConfig.features.join(", ") || "none"}
218
+ Packages: ${updatedPackages.join(", ") || "none"}
219
+ Proceed?`,
220
+ true
221
+ );
222
+
223
+ if (!proceed) {
224
+ return null;
225
+ }
226
+ } else {
227
+ const proceed = await confirmPrompt("Proceed?", true);
228
+ if (!proceed) {
229
+ return null;
230
+ }
231
+ }
232
+
233
+ const installNodeModules = await confirmPrompt(
234
+ "Do you want to install dependencies?",
235
+ true
236
+ );
237
+
238
+ return {
239
+ ...finalConfig,
240
+ installNodeModules,
241
+ };
242
+ }
@@ -0,0 +1,29 @@
1
+ import { CORE_PACKAGES, PACKAGE_MAP } from "../../config/packages.js";
2
+
3
+ /**
4
+ * Generates a unique list of npm packages based on framework and selected features.
5
+ *
6
+ * - Includes core framework packages
7
+ * - Merges feature-based packages
8
+ * - Automatically removes duplicates
9
+ *
10
+ * @param {"vue"|"nuxt"|"vuetify"} framework
11
+ * The selected framework.
12
+ *
13
+ * @param {string[]} features
14
+ * List of enabled features (e.g. ["pinia", "eslint", "tailwind"]).
15
+ *
16
+ * @returns {string[]}
17
+ * A deduplicated array of npm package names.
18
+ */
19
+ export function getPackagesList(framework, features) {
20
+ const set = new Set(CORE_PACKAGES[framework] || []);
21
+
22
+ features.forEach((feature) => {
23
+ PACKAGE_MAP[framework]?.[feature]?.forEach((pkg) => {
24
+ set.add(pkg);
25
+ });
26
+ });
27
+
28
+ return [...set];
29
+ }