apexcss-cli 0.1.1 → 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/bin/apexcss.js +1 -1
- package/cli/commands/build.js +64 -23
- package/cli/commands/init.js +82 -32
- package/cli/index.js +2 -4
- package/cli/utils/framework-detector.js +13 -20
- package/package.json +1 -1
package/bin/apexcss.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Options:
|
|
13
13
|
* -c, --config <path> Config file path (default: ./apex.config.js)
|
|
14
|
-
* -o, --output <dir> Output directory (default:
|
|
14
|
+
* -o, --output <dir> Output directory (default: node_modules/apexcss/dist)
|
|
15
15
|
* --minify Minify output CSS
|
|
16
16
|
* --sourcemap Generate source maps
|
|
17
17
|
* -v, --version Show version
|
package/cli/commands/build.js
CHANGED
|
@@ -248,6 +248,44 @@ export async function buildCommand(options) {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Compile a single SCSS file
|
|
253
|
+
* @param {string} entryFile - Path to entry SCSS file
|
|
254
|
+
* @param {string} outputPath - Path to output CSS file
|
|
255
|
+
* @param {object} compileOptions - Sass compile options
|
|
256
|
+
* @returns {object} - Compilation result with css and sourceMap
|
|
257
|
+
*/
|
|
258
|
+
function compileSassFile(entryFile, outputPath, compileOptions) {
|
|
259
|
+
const result = sass.compile(entryFile, compileOptions);
|
|
260
|
+
writeFileSync(outputPath, result.css);
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Log successful build with file size
|
|
266
|
+
* @param {string} filename - Name of the file
|
|
267
|
+
* @param {string} css - CSS content
|
|
268
|
+
* @param {string} description - Description of what was built
|
|
269
|
+
*/
|
|
270
|
+
function logBuildSuccess(filename, css, description) {
|
|
271
|
+
const contentBytes = Buffer.byteLength(css, 'utf8');
|
|
272
|
+
const sizeKB = (contentBytes / 1024).toFixed(2);
|
|
273
|
+
logger.success(`Built: ${logger.path(filename)} (${sizeKB} KB) [${description}]`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Write source map file if enabled
|
|
278
|
+
* @param {string} outputDir - Output directory
|
|
279
|
+
* @param {string} filename - Base filename without extension
|
|
280
|
+
* @param {object} sourceMap - Source map object
|
|
281
|
+
* @param {boolean} enabled - Whether source maps are enabled
|
|
282
|
+
*/
|
|
283
|
+
function writeSourceMap(outputDir, filename, sourceMap, enabled) {
|
|
284
|
+
if (enabled && sourceMap) {
|
|
285
|
+
writeFileSync(resolve(outputDir, `${filename}.css.map`), JSON.stringify(sourceMap));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
251
289
|
/**
|
|
252
290
|
* Run Sass build
|
|
253
291
|
* @param {string} tempDir - Temp directory path
|
|
@@ -258,41 +296,44 @@ export async function buildCommand(options) {
|
|
|
258
296
|
* @returns {Promise<void>}
|
|
259
297
|
*/
|
|
260
298
|
async function runSassBuild(tempDir, options, outputDir, layers, scssContent) {
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
// Compile SCSS to CSS using Sass
|
|
264
|
-
const result = sass.compile(entryFile, {
|
|
299
|
+
const compileOptions = {
|
|
265
300
|
loadPaths: [tempDir],
|
|
266
301
|
sourceMap: options.sourcemap,
|
|
267
302
|
style: options.minify ? 'compressed' : 'expanded'
|
|
268
|
-
}
|
|
303
|
+
};
|
|
269
304
|
|
|
270
|
-
//
|
|
271
|
-
const
|
|
305
|
+
// Build individual layer files for cascade layer support
|
|
306
|
+
const layerFiles = ['base', 'utilities', 'themes'];
|
|
272
307
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
308
|
+
for (const layer of layerFiles) {
|
|
309
|
+
if (!layers.includes(layer)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
276
312
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
313
|
+
const layerEntry = resolve(tempDir, `${layer}.scss`);
|
|
314
|
+
if (!existsSync(layerEntry)) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
280
317
|
|
|
281
|
-
|
|
282
|
-
|
|
318
|
+
const result = compileSassFile(layerEntry, resolve(outputDir, `${layer}.css`), compileOptions);
|
|
319
|
+
logBuildSuccess(`${layer}.css`, result.css, `${layer} layer`);
|
|
320
|
+
writeSourceMap(outputDir, layer, result.sourceMap, options.sourcemap);
|
|
321
|
+
}
|
|
283
322
|
|
|
284
|
-
//
|
|
285
|
-
if (
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
323
|
+
// Also build combined file if all layers are selected
|
|
324
|
+
if (layers.length === 3) {
|
|
325
|
+
const entryFile = resolve(tempDir, 'apex-entry.scss');
|
|
326
|
+
const result = compileSassFile(entryFile, resolve(outputDir, 'apex.css'), compileOptions);
|
|
327
|
+
logBuildSuccess('apex.css', result.css, 'complete framework');
|
|
328
|
+
writeSourceMap(outputDir, 'apex', result.sourceMap, options.sourcemap);
|
|
289
329
|
}
|
|
290
330
|
|
|
291
331
|
// Output SCSS if requested
|
|
292
332
|
if (options.format === 'scss' || options.format === 'both') {
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
333
|
+
const { filename } = getOutputFilenames(layers);
|
|
334
|
+
const scssFilename = `${filename}.scss`;
|
|
335
|
+
writeFileSync(resolve(outputDir, scssFilename), scssContent);
|
|
336
|
+
logger.success(`Generated: ${logger.path(scssFilename)}`);
|
|
296
337
|
}
|
|
297
338
|
|
|
298
339
|
// Clean up temp directory
|
package/cli/commands/init.js
CHANGED
|
@@ -18,6 +18,25 @@ import { detectFramework, getRecommendedOutputDir, getAvailableFrameworks } from
|
|
|
18
18
|
async function promptForOptions(framework, options) {
|
|
19
19
|
const { default: inquirer } = await import('inquirer');
|
|
20
20
|
|
|
21
|
+
// First, ask if user wants to accept defaults
|
|
22
|
+
const defaultAnswer = await inquirer.prompt([
|
|
23
|
+
{
|
|
24
|
+
type: 'confirm',
|
|
25
|
+
name: 'useDefaults',
|
|
26
|
+
message: `Use default configuration?\n Framework: ${framework.name}\n Add imports: Yes`,
|
|
27
|
+
default: true
|
|
28
|
+
}
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
if (defaultAnswer.useDefaults) {
|
|
32
|
+
return {
|
|
33
|
+
selectedFramework: framework,
|
|
34
|
+
outputDir: options.outputDir || getRecommendedOutputDir(framework.id),
|
|
35
|
+
addImport: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// If not using defaults, show full prompts
|
|
21
40
|
const answers = await inquirer.prompt([
|
|
22
41
|
{
|
|
23
42
|
type: 'list',
|
|
@@ -29,12 +48,6 @@ async function promptForOptions(framework, options) {
|
|
|
29
48
|
value: f.id
|
|
30
49
|
}))
|
|
31
50
|
},
|
|
32
|
-
{
|
|
33
|
-
type: 'input',
|
|
34
|
-
name: 'outputDir',
|
|
35
|
-
message: 'CSS output directory:',
|
|
36
|
-
default: options.outputDir || getRecommendedOutputDir(framework.id)
|
|
37
|
-
},
|
|
38
51
|
{
|
|
39
52
|
type: 'confirm',
|
|
40
53
|
name: 'addImport',
|
|
@@ -48,32 +61,31 @@ async function promptForOptions(framework, options) {
|
|
|
48
61
|
|
|
49
62
|
return {
|
|
50
63
|
selectedFramework: { ...framework, id: answers.framework },
|
|
51
|
-
outputDir:
|
|
64
|
+
outputDir: options.outputDir || getRecommendedOutputDir(framework.id),
|
|
52
65
|
addImport: answers.addImport
|
|
53
66
|
};
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
/**
|
|
57
|
-
* Get user options (interactive
|
|
70
|
+
* Get user options (interactive)
|
|
58
71
|
* @param {object} framework - Detected framework
|
|
59
72
|
* @param {object} options - Command options
|
|
60
73
|
* @param {string} cwd - Current working directory
|
|
61
74
|
* @returns {Promise<{selectedFramework: object, outputDir: string, addImport: boolean}>}
|
|
62
75
|
*/
|
|
63
76
|
async function getUserOptions(framework, options, cwd = process.cwd()) {
|
|
77
|
+
// Always use interactive mode to ask about defaults first
|
|
64
78
|
let result = {
|
|
65
79
|
selectedFramework: framework,
|
|
66
80
|
outputDir: options.outputDir,
|
|
67
81
|
addImport: options.addImport
|
|
68
82
|
};
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
logger.warn('Interactive mode not available, using defaults');
|
|
76
|
-
}
|
|
84
|
+
try {
|
|
85
|
+
result = await promptForOptions(framework, options);
|
|
86
|
+
} catch {
|
|
87
|
+
// Interactive mode failed - fallback to defaults
|
|
88
|
+
logger.warn('Interactive mode not available, using defaults');
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
// Override with CLI framework option if provided
|
|
@@ -211,22 +223,69 @@ export async function initCommand(options) {
|
|
|
211
223
|
addFrameworkImport(cwd, selectedFramework, outputDir);
|
|
212
224
|
}
|
|
213
225
|
|
|
226
|
+
// Add npm scripts to package.json
|
|
227
|
+
setupPackageJsonScripts(cwd);
|
|
228
|
+
|
|
214
229
|
logger.newline();
|
|
215
230
|
logger.success('ApexCSS initialized successfully!');
|
|
216
231
|
logger.newline();
|
|
217
232
|
logger.info('Next steps:');
|
|
218
233
|
logger.list([
|
|
219
234
|
`Edit ${logger.path(options.configPath)} to customize your configuration`,
|
|
220
|
-
`Run ${logger.cmd('
|
|
221
|
-
`Run ${logger.cmd('
|
|
235
|
+
`Run ${logger.cmd('npm run apexcss:build')} to generate your CSS`,
|
|
236
|
+
`Run ${logger.cmd('npm run apexcss:watch')} during development`
|
|
222
237
|
]);
|
|
223
238
|
logger.newline();
|
|
224
239
|
}
|
|
225
240
|
|
|
226
241
|
/**
|
|
227
|
-
*
|
|
228
|
-
* @
|
|
242
|
+
* Add npm scripts for ApexCSS to package.json
|
|
243
|
+
* @param {string} cwd - Current working directory
|
|
229
244
|
*/
|
|
245
|
+
function setupPackageJsonScripts(cwd) {
|
|
246
|
+
const packageJsonPath = resolve(cwd, 'package.json');
|
|
247
|
+
|
|
248
|
+
if (!existsSync(packageJsonPath)) {
|
|
249
|
+
logger.warn('No package.json found. Skipping npm scripts setup.');
|
|
250
|
+
logger.info('To create one, run: npm init');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
256
|
+
|
|
257
|
+
// Ensure scripts object exists
|
|
258
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
259
|
+
|
|
260
|
+
// Add ApexCSS scripts if they don't exist
|
|
261
|
+
const scripts = {
|
|
262
|
+
'apexcss:build': 'npx apexcss build',
|
|
263
|
+
'apexcss:watch': 'npx apexcss watch'
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
let scriptsAdded = false;
|
|
267
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
268
|
+
if (!packageJson.scripts[name]) {
|
|
269
|
+
packageJson.scripts[name] = command;
|
|
270
|
+
scriptsAdded = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (scriptsAdded) {
|
|
275
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
276
|
+
logger.success('Added npm scripts to package.json:');
|
|
277
|
+
logger.list([
|
|
278
|
+
`${logger.cmd('npm run apexcss:build')} - Build CSS once`,
|
|
279
|
+
`${logger.cmd('npm run apexcss:watch')} - Watch for changes`
|
|
280
|
+
]);
|
|
281
|
+
} else {
|
|
282
|
+
logger.info('ApexCSS npm scripts already exist in package.json');
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
logger.warn(`Could not update package.json: ${error.message}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
230
289
|
/**
|
|
231
290
|
* Prompt for overwrite confirmation
|
|
232
291
|
* @returns {Promise<{overwrite: boolean}>}
|
|
@@ -260,18 +319,10 @@ const CASCADE_LAYER_IMPORTS = `@layer base, utilities, themes;
|
|
|
260
319
|
/**
|
|
261
320
|
* Get the appropriate import statement for the framework
|
|
262
321
|
* @param {string} frameworkId - Framework identifier
|
|
263
|
-
* @param {string}
|
|
322
|
+
* @param {string} [_outputDir] - Output directory (unused - kept for API compatibility)
|
|
264
323
|
* @returns {string} - Import statement
|
|
265
324
|
*/
|
|
266
|
-
export function getImportStatement(frameworkId,
|
|
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
|
-
|
|
325
|
+
export function getImportStatement(frameworkId, _outputDir) {
|
|
275
326
|
// All CSS-based frameworks now use cascade layers with node_modules imports
|
|
276
327
|
switch (frameworkId) {
|
|
277
328
|
case 'angular':
|
|
@@ -280,10 +331,9 @@ export function getImportStatement(frameworkId, outputDir) {
|
|
|
280
331
|
case 'svelte':
|
|
281
332
|
case 'vanilla':
|
|
282
333
|
case 'astro':
|
|
283
|
-
return CASCADE_LAYER_IMPORTS;
|
|
284
334
|
case 'next':
|
|
285
|
-
// Next.js
|
|
286
|
-
return
|
|
335
|
+
// Next.js uses globals.css for global styles with cascade layers
|
|
336
|
+
return CASCADE_LAYER_IMPORTS;
|
|
287
337
|
case 'nuxt':
|
|
288
338
|
return '// Add to nuxt.config.ts:\n// css: [\'apexcss/base\', \'apexcss/utilities\', \'apexcss/themes\']\n';
|
|
289
339
|
default:
|
package/cli/index.js
CHANGED
|
@@ -33,7 +33,7 @@ export function cli(args) {
|
|
|
33
33
|
// Global options
|
|
34
34
|
program
|
|
35
35
|
.option('-c, --config <path>', 'path to config file', './apex.config.js')
|
|
36
|
-
.option('-o, --output <dir>', 'output directory', '
|
|
36
|
+
.option('-o, --output <dir>', 'output directory', 'node_modules/apexcss/dist')
|
|
37
37
|
.option('--minify', 'minify output CSS', false)
|
|
38
38
|
.option('--sourcemap', 'generate source maps', false);
|
|
39
39
|
|
|
@@ -41,8 +41,7 @@ export function cli(args) {
|
|
|
41
41
|
program
|
|
42
42
|
.command('init')
|
|
43
43
|
.description('Initialize ApexCSS configuration in your project')
|
|
44
|
-
.option('-f, --framework <name>', 'specify framework (react, vue, angular, svelte, astro, next, nuxt, vanilla
|
|
45
|
-
.option('--no-interactive', 'skip interactive prompts')
|
|
44
|
+
.option('-f, --framework <name>', 'specify framework (react, vue, angular, svelte, astro, next, nuxt, vanilla)')
|
|
46
45
|
.option('--no-import', 'skip adding imports to entry files')
|
|
47
46
|
.action(async (options) => {
|
|
48
47
|
try {
|
|
@@ -50,7 +49,6 @@ export function cli(args) {
|
|
|
50
49
|
configPath: program.opts().config,
|
|
51
50
|
outputDir: program.opts().output,
|
|
52
51
|
framework: options.framework,
|
|
53
|
-
interactive: options.interactive,
|
|
54
52
|
addImport: options.import
|
|
55
53
|
});
|
|
56
54
|
} catch (error) {
|
|
@@ -13,10 +13,11 @@ export const FRAMEWORKS = {
|
|
|
13
13
|
next: {
|
|
14
14
|
name: 'Next.js',
|
|
15
15
|
detect: (pkg) => pkg.dependencies?.next || pkg.devDependencies?.next,
|
|
16
|
-
entryFiles: ['src/app/
|
|
17
|
-
importStatement: 'import \'apexcss\';\n',
|
|
18
|
-
cssConfig: '//
|
|
19
|
-
configFile: 'next.config.js'
|
|
16
|
+
entryFiles: ['src/app/globals.css', 'app/globals.css', 'src/styles/globals.css', 'styles/globals.css'],
|
|
17
|
+
importStatement: '@import \'apexcss\';\n',
|
|
18
|
+
cssConfig: '// Add to globals.css:\n@import \'apexcss/base\';\n@import \'apexcss/utilities\';\n@import \'apexcss/themes\';\n',
|
|
19
|
+
configFile: 'next.config.js',
|
|
20
|
+
fallbackFile: 'src/app/globals.css'
|
|
20
21
|
},
|
|
21
22
|
nuxt: {
|
|
22
23
|
name: 'Nuxt',
|
|
@@ -44,7 +45,8 @@ export const FRAMEWORKS = {
|
|
|
44
45
|
detect: (pkg) => pkg.dependencies?.['@angular/core'],
|
|
45
46
|
entryFiles: ['src/styles.css', 'src/styles.scss', 'angular.json'],
|
|
46
47
|
importStatement: '@import \'apexcss\';\n',
|
|
47
|
-
configFile: 'angular.json'
|
|
48
|
+
configFile: 'angular.json',
|
|
49
|
+
fallbackFile: 'src/styles.css'
|
|
48
50
|
},
|
|
49
51
|
svelte: {
|
|
50
52
|
name: 'Svelte',
|
|
@@ -149,23 +151,14 @@ export function getAvailableFrameworks() {
|
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/**
|
|
152
|
-
* Get
|
|
153
|
-
* @param {string}
|
|
154
|
+
* Get the default output directory for CSS builds
|
|
155
|
+
* @param {string} [_frameworkId] - Framework identifier (unused - kept for API compatibility)
|
|
154
156
|
* @returns {string} - Recommended output directory
|
|
155
157
|
*/
|
|
156
|
-
export function getRecommendedOutputDir(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
react: './dist/',
|
|
161
|
-
vue: './dist/',
|
|
162
|
-
angular: './dist/',
|
|
163
|
-
svelte: './dist/',
|
|
164
|
-
astro: './dist/',
|
|
165
|
-
vanilla: './dist/'
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
return recommendations[frameworkId] || './dist/';
|
|
158
|
+
export function getRecommendedOutputDir(_frameworkId) {
|
|
159
|
+
// All frameworks output to node_modules/apexcss/dist by default
|
|
160
|
+
// This aligns with where the CSS source files reside (node_modules/apexcss/src)
|
|
161
|
+
return 'node_modules/apexcss/dist';
|
|
169
162
|
}
|
|
170
163
|
|
|
171
164
|
/**
|