apexcss-cli 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.
- package/README.md +284 -0
- package/bin/apexcss.js +23 -0
- package/cli/commands/build.js +376 -0
- package/cli/commands/doctor.js +286 -0
- package/cli/commands/init.js +339 -0
- package/cli/commands/watch.js +150 -0
- package/cli/index.js +129 -0
- package/cli/utils/config-builder.js +1934 -0
- package/cli/utils/config-loader.js +963 -0
- package/cli/utils/framework-detector.js +189 -0
- package/cli/utils/logger.js +121 -0
- package/package.json +72 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command - Check system setup and diagnose issues
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { detectFramework } from '../utils/framework-detector.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run diagnostic checks
|
|
13
|
+
*/
|
|
14
|
+
export async function doctorCommand() {
|
|
15
|
+
logger.header('ApexCSS Doctor');
|
|
16
|
+
logger.newline();
|
|
17
|
+
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
|
|
20
|
+
// Run all checks (synchronous)
|
|
21
|
+
const results = [
|
|
22
|
+
checkNodeVersion(),
|
|
23
|
+
checkPackageManager(),
|
|
24
|
+
checkApexcssInstallation(cwd),
|
|
25
|
+
checkConfigFile(cwd),
|
|
26
|
+
checkFramework(cwd),
|
|
27
|
+
checkVite(cwd)
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Display results
|
|
31
|
+
logger.header('Diagnostic Results');
|
|
32
|
+
logger.newline();
|
|
33
|
+
|
|
34
|
+
let hasErrors = false;
|
|
35
|
+
let hasWarnings = false;
|
|
36
|
+
|
|
37
|
+
for (const result of results) {
|
|
38
|
+
if (result.status === 'ok') {
|
|
39
|
+
logger.success(`${result.name}: ${result.message}`);
|
|
40
|
+
} else if (result.status === 'warn') {
|
|
41
|
+
hasWarnings = true;
|
|
42
|
+
logger.warn(`${result.name}: ${result.message}`);
|
|
43
|
+
if (result.fix) {
|
|
44
|
+
logger.info(` Fix: ${result.fix}`);
|
|
45
|
+
}
|
|
46
|
+
} else if (result.status === 'error') {
|
|
47
|
+
hasErrors = true;
|
|
48
|
+
logger.error(`${result.name}: ${result.message}`);
|
|
49
|
+
if (result.fix) {
|
|
50
|
+
logger.info(` Fix: ${result.fix}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logger.newline();
|
|
56
|
+
|
|
57
|
+
if (hasErrors) {
|
|
58
|
+
logger.error('Some checks failed. Please fix the issues above.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
} else if (hasWarnings) {
|
|
61
|
+
logger.warn('Some checks have warnings. Review the messages above.');
|
|
62
|
+
} else {
|
|
63
|
+
logger.success('All checks passed! Your system is ready.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check Node.js version
|
|
69
|
+
* @returns {object}
|
|
70
|
+
*/
|
|
71
|
+
function checkNodeVersion() {
|
|
72
|
+
const nodeVersion = process.version;
|
|
73
|
+
const majorVersion = Number.parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
74
|
+
|
|
75
|
+
if (majorVersion >= 18) {
|
|
76
|
+
return {
|
|
77
|
+
name: 'Node.js',
|
|
78
|
+
status: 'ok',
|
|
79
|
+
message: `v${nodeVersion} (supported)`
|
|
80
|
+
};
|
|
81
|
+
} else if (majorVersion >= 16) {
|
|
82
|
+
return {
|
|
83
|
+
name: 'Node.js',
|
|
84
|
+
status: 'warn',
|
|
85
|
+
message: `v${nodeVersion} (minimum recommended is v18+)`,
|
|
86
|
+
fix: 'Upgrade to Node.js v18 or later'
|
|
87
|
+
};
|
|
88
|
+
} else {
|
|
89
|
+
return {
|
|
90
|
+
name: 'Node.js',
|
|
91
|
+
status: 'error',
|
|
92
|
+
message: `v${nodeVersion} (not supported)`,
|
|
93
|
+
fix: 'Upgrade to Node.js v18 or later'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check package manager availability
|
|
100
|
+
* @returns {object}
|
|
101
|
+
*/
|
|
102
|
+
function checkPackageManager() {
|
|
103
|
+
const managers = [
|
|
104
|
+
{ name: 'npm', cmd: 'npm --version' },
|
|
105
|
+
{ name: 'pnpm', cmd: 'pnpm --version' },
|
|
106
|
+
{ name: 'yarn', cmd: 'yarn --version' }
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const available = [];
|
|
110
|
+
|
|
111
|
+
for (const manager of managers) {
|
|
112
|
+
try {
|
|
113
|
+
const version = execSync(manager.cmd, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
114
|
+
available.push(`${manager.name} v${version}`);
|
|
115
|
+
} catch {
|
|
116
|
+
// Package manager not available - expected if not installed
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (available.length > 0) {
|
|
121
|
+
return {
|
|
122
|
+
name: 'Package Manager',
|
|
123
|
+
status: 'ok',
|
|
124
|
+
message: available.join(', ')
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
name: 'Package Manager',
|
|
130
|
+
status: 'error',
|
|
131
|
+
message: 'None detected',
|
|
132
|
+
fix: 'Install npm (comes with Node.js) or pnpm/yarn'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if ApexCSS is installed
|
|
138
|
+
* @param {string} cwd
|
|
139
|
+
* @returns {object}
|
|
140
|
+
*/
|
|
141
|
+
function checkApexcssInstallation(cwd) {
|
|
142
|
+
const packageJsonPath = resolve(cwd, 'package.json');
|
|
143
|
+
|
|
144
|
+
if (!existsSync(packageJsonPath)) {
|
|
145
|
+
return {
|
|
146
|
+
name: 'ApexCSS Installation',
|
|
147
|
+
status: 'warn',
|
|
148
|
+
message: 'No package.json found',
|
|
149
|
+
fix: 'Run "npm init" first, then "npm install apexcss"'
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
155
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
156
|
+
|
|
157
|
+
if (deps.apexcss) {
|
|
158
|
+
return {
|
|
159
|
+
name: 'ApexCSS Installation',
|
|
160
|
+
status: 'ok',
|
|
161
|
+
message: `v${deps.apexcss} installed`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
name: 'ApexCSS Installation',
|
|
167
|
+
status: 'warn',
|
|
168
|
+
message: 'Not installed in this project',
|
|
169
|
+
fix: 'Run "npm install apexcss"'
|
|
170
|
+
};
|
|
171
|
+
} catch {
|
|
172
|
+
return {
|
|
173
|
+
name: 'ApexCSS Installation',
|
|
174
|
+
status: 'error',
|
|
175
|
+
message: 'Could not parse package.json'
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check for config file
|
|
182
|
+
* @param {string} cwd
|
|
183
|
+
* @returns {object}
|
|
184
|
+
*/
|
|
185
|
+
function checkConfigFile(cwd) {
|
|
186
|
+
const configPath = resolve(cwd, 'apex.config.js');
|
|
187
|
+
|
|
188
|
+
if (existsSync(configPath)) {
|
|
189
|
+
return {
|
|
190
|
+
name: 'Configuration File',
|
|
191
|
+
status: 'ok',
|
|
192
|
+
message: 'apex.config.js exists'
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
name: 'Configuration File',
|
|
198
|
+
status: 'warn',
|
|
199
|
+
message: 'apex.config.js not found',
|
|
200
|
+
fix: 'Run "npx apexcss init" to create a config file'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check framework detection
|
|
206
|
+
* @param {string} cwd
|
|
207
|
+
* @returns {object}
|
|
208
|
+
*/
|
|
209
|
+
function checkFramework(cwd) {
|
|
210
|
+
const framework = detectFramework(cwd);
|
|
211
|
+
|
|
212
|
+
if (framework.detected) {
|
|
213
|
+
return {
|
|
214
|
+
name: 'Framework',
|
|
215
|
+
status: 'ok',
|
|
216
|
+
message: `${framework.name} detected`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!framework.hasPackageJson) {
|
|
221
|
+
return {
|
|
222
|
+
name: 'Framework',
|
|
223
|
+
status: 'warn',
|
|
224
|
+
message: 'No package.json found',
|
|
225
|
+
fix: 'Run "npm init" to create a project'
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
name: 'Framework',
|
|
231
|
+
status: 'warn',
|
|
232
|
+
message: 'Could not detect framework (assuming vanilla)',
|
|
233
|
+
fix: 'Specify framework with "npx apexcss init --framework=<name>"'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check for Vite
|
|
239
|
+
* @param {string} cwd
|
|
240
|
+
* @returns {object}
|
|
241
|
+
*/
|
|
242
|
+
function checkVite(cwd) {
|
|
243
|
+
const packageJsonPath = resolve(cwd, 'package.json');
|
|
244
|
+
|
|
245
|
+
if (!existsSync(packageJsonPath)) {
|
|
246
|
+
return {
|
|
247
|
+
name: 'Build Tool',
|
|
248
|
+
status: 'warn',
|
|
249
|
+
message: 'No package.json found'
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
255
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
256
|
+
|
|
257
|
+
if (deps.vite) {
|
|
258
|
+
return {
|
|
259
|
+
name: 'Build Tool',
|
|
260
|
+
status: 'ok',
|
|
261
|
+
message: `Vite v${deps.vite} detected (recommended)`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (deps.webpack || deps['@angular/cli'] || deps.next || deps.nuxt) {
|
|
266
|
+
return {
|
|
267
|
+
name: 'Build Tool',
|
|
268
|
+
status: 'ok',
|
|
269
|
+
message: 'Build tool detected'
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
name: 'Build Tool',
|
|
275
|
+
status: 'warn',
|
|
276
|
+
message: 'No build tool detected',
|
|
277
|
+
fix: 'Install Vite for best experience: "npm install -D vite"'
|
|
278
|
+
};
|
|
279
|
+
} catch {
|
|
280
|
+
return {
|
|
281
|
+
name: 'Build Tool',
|
|
282
|
+
status: 'warn',
|
|
283
|
+
message: 'Could not check build tools'
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init command - Initialize ApexCSS configuration in user's project
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { mkdir } from 'node:fs/promises';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { generateSampleConfig } from '../utils/config-loader.js';
|
|
10
|
+
import { detectFramework, getRecommendedOutputDir, getAvailableFrameworks } from '../utils/framework-detector.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Prompt user for initialization options
|
|
14
|
+
* @param {object} framework - Detected framework
|
|
15
|
+
* @param {object} options - Current options
|
|
16
|
+
* @returns {Promise<{selectedFramework: object, outputDir: string, addImport: boolean}>}
|
|
17
|
+
*/
|
|
18
|
+
async function promptForOptions(framework, options) {
|
|
19
|
+
const { default: inquirer } = await import('inquirer');
|
|
20
|
+
|
|
21
|
+
const answers = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'list',
|
|
24
|
+
name: 'framework',
|
|
25
|
+
message: 'Select your framework:',
|
|
26
|
+
default: framework.id,
|
|
27
|
+
choices: getAvailableFrameworks().map(f => ({
|
|
28
|
+
name: f.name,
|
|
29
|
+
value: f.id
|
|
30
|
+
}))
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'input',
|
|
34
|
+
name: 'outputDir',
|
|
35
|
+
message: 'CSS output directory:',
|
|
36
|
+
default: options.outputDir || getRecommendedOutputDir(framework.id)
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'confirm',
|
|
40
|
+
name: 'addImport',
|
|
41
|
+
message: framework.entryFile
|
|
42
|
+
? `Add import to ${framework.entryFile}?`
|
|
43
|
+
: 'Add import to your main entry file?',
|
|
44
|
+
default: true,
|
|
45
|
+
when: () => framework.entryFile || framework.id !== 'vanilla'
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
selectedFramework: { ...framework, id: answers.framework },
|
|
51
|
+
outputDir: answers.outputDir,
|
|
52
|
+
addImport: answers.addImport
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get user options (interactive or CLI)
|
|
58
|
+
* @param {object} framework - Detected framework
|
|
59
|
+
* @param {object} options - Command options
|
|
60
|
+
* @param {string} cwd - Current working directory
|
|
61
|
+
* @returns {Promise<{selectedFramework: object, outputDir: string, addImport: boolean}>}
|
|
62
|
+
*/
|
|
63
|
+
async function getUserOptions(framework, options, cwd = process.cwd()) {
|
|
64
|
+
let result = {
|
|
65
|
+
selectedFramework: framework,
|
|
66
|
+
outputDir: options.outputDir,
|
|
67
|
+
addImport: options.addImport
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (options.interactive) {
|
|
71
|
+
try {
|
|
72
|
+
result = await promptForOptions(framework, options);
|
|
73
|
+
} catch {
|
|
74
|
+
// Interactive mode failed - fallback to defaults
|
|
75
|
+
logger.warn('Interactive mode not available, using defaults');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Override with CLI framework option if provided
|
|
80
|
+
if (options.framework) {
|
|
81
|
+
// Update entryFile based on the new framework
|
|
82
|
+
const { FRAMEWORKS, getAvailableFrameworks } = await import('../utils/framework-detector.js');
|
|
83
|
+
const availableFrameworks = getAvailableFrameworks();
|
|
84
|
+
const frameworkInfo = availableFrameworks.find(f => f.id === options.framework);
|
|
85
|
+
const frameworkDef = FRAMEWORKS[options.framework];
|
|
86
|
+
|
|
87
|
+
if (frameworkDef) {
|
|
88
|
+
const entryFiles = frameworkDef.entryFiles || [];
|
|
89
|
+
const existingEntry = entryFiles.find(file =>
|
|
90
|
+
existsSync(resolve(cwd, file))
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
result.selectedFramework = {
|
|
94
|
+
...framework,
|
|
95
|
+
...frameworkDef,
|
|
96
|
+
id: options.framework,
|
|
97
|
+
name: frameworkInfo?.name || options.framework,
|
|
98
|
+
entryFile: existingEntry || frameworkDef.fallbackFile
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Handle existing config file
|
|
108
|
+
* @param {string} configPath - Path to config file
|
|
109
|
+
* @param {boolean} useInteractive - Whether interactive mode is enabled
|
|
110
|
+
* @returns {Promise<boolean>} - True if should continue
|
|
111
|
+
*/
|
|
112
|
+
async function handleExistingConfig(configPath, useInteractive) {
|
|
113
|
+
if (!existsSync(configPath)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
logger.warn(`Config file already exists at ${configPath}`);
|
|
118
|
+
const { overwrite } = useInteractive ? await promptOverwrite() : { overwrite: false };
|
|
119
|
+
|
|
120
|
+
if (!overwrite) {
|
|
121
|
+
logger.info('Skipping config creation');
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Setup gitignore for output directory
|
|
130
|
+
* @param {string} cwd - Current working directory
|
|
131
|
+
* @param {string} outputDir - Output directory
|
|
132
|
+
*/
|
|
133
|
+
function setupGitignore(cwd, outputDir) {
|
|
134
|
+
const gitignorePath = resolve(cwd, '.gitignore');
|
|
135
|
+
const gitignoreEntry = `# ApexCSS generated files\n${outputDir.replace(/^\.\//, '')}\n`;
|
|
136
|
+
|
|
137
|
+
if (existsSync(gitignorePath)) {
|
|
138
|
+
const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
|
|
139
|
+
if (!gitignoreContent.includes(outputDir)) {
|
|
140
|
+
logger.info(`Add to ${logger.path('.gitignore')}: ${outputDir}`);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
writeFileSync(gitignorePath, gitignoreEntry);
|
|
144
|
+
logger.success(`Created ${logger.path('.gitignore')}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Add import to framework entry file
|
|
150
|
+
* @param {string} cwd - Current working directory
|
|
151
|
+
* @param {object} framework - Selected framework
|
|
152
|
+
* @param {string} outputDir - Output directory
|
|
153
|
+
*/
|
|
154
|
+
function addFrameworkImport(cwd, framework, outputDir) {
|
|
155
|
+
if (!framework.entryFile) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entryFilePath = resolve(cwd, framework.entryFile);
|
|
160
|
+
|
|
161
|
+
if (!existsSync(entryFilePath)) {
|
|
162
|
+
logger.warn(`Entry file not found: ${framework.entryFile}`);
|
|
163
|
+
logger.info('Manually add import to your main entry file');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const importStatement = getImportStatement(framework.id, outputDir);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
addImportToFile(entryFilePath, importStatement, framework.id);
|
|
171
|
+
logger.success(`Added import to ${logger.path(framework.entryFile)}`);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.warn(`Could not add import automatically: ${error.message}`);
|
|
174
|
+
logger.info(`Manually add: ${logger.cmd(importStatement.trim())}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Initialize ApexCSS in the user's project
|
|
180
|
+
* @param {object} options - Command options
|
|
181
|
+
*/
|
|
182
|
+
export async function initCommand(options) {
|
|
183
|
+
const cwd = process.cwd();
|
|
184
|
+
|
|
185
|
+
logger.header('ApexCSS Initialization');
|
|
186
|
+
logger.newline();
|
|
187
|
+
|
|
188
|
+
const framework = detectFramework(cwd);
|
|
189
|
+
logger.info(`Detected framework: ${framework.name}`);
|
|
190
|
+
logger.newline();
|
|
191
|
+
|
|
192
|
+
const { selectedFramework, outputDir, addImport } = await getUserOptions(framework, options, cwd);
|
|
193
|
+
|
|
194
|
+
const outputPath = resolve(cwd, outputDir);
|
|
195
|
+
await mkdir(outputPath, { recursive: true });
|
|
196
|
+
|
|
197
|
+
const configPath = resolve(cwd, options.configPath);
|
|
198
|
+
const shouldContinue = await handleExistingConfig(configPath, options.interactive);
|
|
199
|
+
|
|
200
|
+
if (!shouldContinue) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const configContent = generateSampleConfig();
|
|
205
|
+
writeFileSync(configPath, configContent);
|
|
206
|
+
logger.success(`Created config file: ${logger.path(options.configPath)}`);
|
|
207
|
+
|
|
208
|
+
setupGitignore(cwd, outputDir);
|
|
209
|
+
|
|
210
|
+
if (addImport) {
|
|
211
|
+
addFrameworkImport(cwd, selectedFramework, outputDir);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
logger.newline();
|
|
215
|
+
logger.success('ApexCSS initialized successfully!');
|
|
216
|
+
logger.newline();
|
|
217
|
+
logger.info('Next steps:');
|
|
218
|
+
logger.list([
|
|
219
|
+
`Edit ${logger.path(options.configPath)} to customize your configuration`,
|
|
220
|
+
`Run ${logger.cmd('npx apexcss build')} to generate your CSS`,
|
|
221
|
+
`Run ${logger.cmd('npx apexcss watch')} during development`
|
|
222
|
+
]);
|
|
223
|
+
logger.newline();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Prompt for overwrite confirmation
|
|
228
|
+
* @returns {Promise<{overwrite: boolean}>}
|
|
229
|
+
*/
|
|
230
|
+
/**
|
|
231
|
+
* Prompt for overwrite confirmation
|
|
232
|
+
* @returns {Promise<{overwrite: boolean}>}
|
|
233
|
+
*/
|
|
234
|
+
export async function promptOverwrite() {
|
|
235
|
+
try {
|
|
236
|
+
const { default: inquirer } = await import('inquirer');
|
|
237
|
+
return await inquirer.prompt([{
|
|
238
|
+
type: 'confirm',
|
|
239
|
+
name: 'overwrite',
|
|
240
|
+
message: 'Overwrite existing config file?',
|
|
241
|
+
default: false
|
|
242
|
+
}]);
|
|
243
|
+
} catch {
|
|
244
|
+
// Inquirer not available - default to not overwriting
|
|
245
|
+
return { overwrite: false };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* CSS cascade layers import statement for all frameworks
|
|
251
|
+
* This uses the standard apexcss package imports with cascade layers
|
|
252
|
+
*/
|
|
253
|
+
const CASCADE_LAYER_IMPORTS = `@layer base, utilities, themes;
|
|
254
|
+
|
|
255
|
+
@import 'apexcss/base' layer(base);
|
|
256
|
+
@import 'apexcss/utilities' layer(utilities);
|
|
257
|
+
@import 'apexcss/themes' layer(themes);
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get the appropriate import statement for the framework
|
|
262
|
+
* @param {string} frameworkId - Framework identifier
|
|
263
|
+
* @param {string} outputDir - Output directory
|
|
264
|
+
* @returns {string} - Import statement
|
|
265
|
+
*/
|
|
266
|
+
export function getImportStatement(frameworkId, outputDir) {
|
|
267
|
+
// Normalize path: remove leading ./ and trailing slashes
|
|
268
|
+
let cleanPath = outputDir.replace(/^\.\//, '').replace(/\/+$/, '');
|
|
269
|
+
|
|
270
|
+
// If path is empty after cleanup, use 'dist'
|
|
271
|
+
if (!cleanPath) {
|
|
272
|
+
cleanPath = 'dist';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// All CSS-based frameworks now use cascade layers with node_modules imports
|
|
276
|
+
switch (frameworkId) {
|
|
277
|
+
case 'angular':
|
|
278
|
+
case 'react':
|
|
279
|
+
case 'vue':
|
|
280
|
+
case 'svelte':
|
|
281
|
+
case 'vanilla':
|
|
282
|
+
case 'astro':
|
|
283
|
+
return CASCADE_LAYER_IMPORTS;
|
|
284
|
+
case 'next':
|
|
285
|
+
// Next.js needs JS imports in layout.tsx, not CSS @imports (CSS @import doesn't resolve node_modules)
|
|
286
|
+
return 'import \'apexcss/base\';\nimport \'apexcss/utilities\';\nimport \'apexcss/themes\';\n';
|
|
287
|
+
case 'nuxt':
|
|
288
|
+
return '// Add to nuxt.config.ts:\n// css: [\'apexcss/base\', \'apexcss/utilities\', \'apexcss/themes\']\n';
|
|
289
|
+
default:
|
|
290
|
+
return CASCADE_LAYER_IMPORTS;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Add import statement to a file
|
|
296
|
+
* @param {string} filePath - Path to the file
|
|
297
|
+
* @param {string} importStatement - Import statement to add
|
|
298
|
+
* @param {string} frameworkId - Framework identifier
|
|
299
|
+
*/
|
|
300
|
+
export function addImportToFile(filePath, importStatement, frameworkId) {
|
|
301
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
302
|
+
|
|
303
|
+
// Check if already imported
|
|
304
|
+
if (content.includes('apexcss') || content.includes('apex.css')) {
|
|
305
|
+
return; // Already has import
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let newContent;
|
|
309
|
+
|
|
310
|
+
// Check if this is a CSS file (for CSS cascade layer imports)
|
|
311
|
+
const isCSSFile = filePath.endsWith('.css') || filePath.endsWith('.scss');
|
|
312
|
+
|
|
313
|
+
if (isCSSFile) {
|
|
314
|
+
// For CSS files, add cascade layer imports at the top
|
|
315
|
+
newContent = importStatement + content;
|
|
316
|
+
} else if (frameworkId === 'react' || frameworkId === 'vue' || frameworkId === 'svelte' || frameworkId === 'astro' || frameworkId === 'vanilla') {
|
|
317
|
+
// Add after other JS imports, before code
|
|
318
|
+
const lines = content.split('\n');
|
|
319
|
+
let lastImportIndex = -1;
|
|
320
|
+
|
|
321
|
+
for (let i = 0; i < lines.length; i++) {
|
|
322
|
+
if (lines[i].trim().startsWith('import ') || lines[i].trim().startsWith('require(')) {
|
|
323
|
+
lastImportIndex = i;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (lastImportIndex >= 0) {
|
|
328
|
+
lines.splice(lastImportIndex + 1, 0, importStatement.trim());
|
|
329
|
+
newContent = lines.join('\n');
|
|
330
|
+
} else {
|
|
331
|
+
newContent = importStatement + content;
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
// Default: add at the top
|
|
335
|
+
newContent = importStatement + content;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
writeFileSync(filePath, newContent);
|
|
339
|
+
}
|