apexcss-cli 0.1.0 → 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 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: ./src/apexcss/)
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
@@ -7,6 +7,7 @@ import { resolve, dirname } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { loadConfig } from '../utils/config-loader.js';
9
9
  import { logger } from '../utils/logger.js';
10
+ import * as sass from 'sass';
10
11
 
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = dirname(__filename);
@@ -228,11 +229,11 @@ export async function buildCommand(options) {
228
229
  const entryContent = generateLayerEntry(layers);
229
230
  writeFileSync(resolve(tempDir, 'apex-entry.scss'), entryContent);
230
231
 
231
- // Build using Vite programmatically
232
+ // Build using Sass compiler
232
233
  logger.info('Compiling CSS...');
233
234
 
234
235
  try {
235
- await runViteBuild(tempDir, options, outputDir, layers, scssContent);
236
+ await runSassBuild(tempDir, options, outputDir, layers, scssContent);
236
237
 
237
238
  const duration = Date.now() - startTime;
238
239
  logger.newline();
@@ -248,7 +249,45 @@ export async function buildCommand(options) {
248
249
  }
249
250
 
250
251
  /**
251
- * Run Vite build
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
+
289
+ /**
290
+ * Run Sass build
252
291
  * @param {string} tempDir - Temp directory path
253
292
  * @param {object} options - Build options
254
293
  * @param {string} outputDir - Output directory path
@@ -256,82 +295,45 @@ export async function buildCommand(options) {
256
295
  * @param {string} scssContent - SCSS content
257
296
  * @returns {Promise<void>}
258
297
  */
259
- async function runViteBuild(tempDir, options, outputDir, layers, scssContent) {
260
- const { build } = await import('vite');
261
- const cwd = process.cwd();
262
-
263
- // Look for postcss config in user's project
264
- const userPostcssConfig = resolve(cwd, 'postcss.config.js');
265
- const apexcssPostcssConfig = resolve(cwd, 'node_modules', 'apexcss', 'postcss.config.js');
266
-
267
- let postcssConfig;
268
- if (existsSync(userPostcssConfig)) {
269
- postcssConfig = userPostcssConfig;
270
- } else if (existsSync(apexcssPostcssConfig)) {
271
- postcssConfig = apexcssPostcssConfig;
272
- } else {
273
- postcssConfig = undefined;
274
- }
275
-
276
- await build({
277
- configFile: false,
278
- css: {
279
- postcss: postcssConfig
280
- },
281
- build: {
282
- cssCodeSplit: false,
283
- sourcemap: options.sourcemap,
284
- minify: options.minify ? 'esbuild' : false,
285
- outDir: tempDir,
286
- rollupOptions: {
287
- input: {
288
- apex: resolve(tempDir, 'apex-entry.scss')
289
- },
290
- output: {
291
- entryFileNames: '[name].js',
292
- assetFileNames: () => '[name][extname]'
293
- }
294
- }
298
+ async function runSassBuild(tempDir, options, outputDir, layers, scssContent) {
299
+ const compileOptions = {
300
+ loadPaths: [tempDir],
301
+ sourceMap: options.sourcemap,
302
+ style: options.minify ? 'compressed' : 'expanded'
303
+ };
304
+
305
+ // Build individual layer files for cascade layer support
306
+ const layerFiles = ['base', 'utilities', 'themes'];
307
+
308
+ for (const layer of layerFiles) {
309
+ if (!layers.includes(layer)) {
310
+ continue;
295
311
  }
296
- });
297
-
298
- // Get output filenames based on layers
299
- const { filename, description } = getOutputFilenames(layers);
300
312
 
301
- // Copy CSS from temp to output
302
- const generatedCssPath = findGeneratedCss(tempDir);
303
- const finalCssPath = resolve(outputDir, `${filename}.css`);
313
+ const layerEntry = resolve(tempDir, `${layer}.scss`);
314
+ if (!existsSync(layerEntry)) {
315
+ continue;
316
+ }
304
317
 
305
- if (!generatedCssPath) {
306
- throw new Error('CSS file was not generated');
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);
307
321
  }
308
322
 
309
- const cssContent = readFileSync(generatedCssPath, 'utf-8');
310
- writeFileSync(finalCssPath, cssContent);
311
-
312
- // Calculate file size
313
- const contentBytes = Buffer.byteLength(cssContent, 'utf8');
314
- const sizeKB = (contentBytes / 1024).toFixed(2);
315
-
316
- const filePath = logger.path(`${filename}.css`);
317
- logger.success(`Built: ${filePath} (${sizeKB} KB) [${description}]`);
318
-
319
- // Copy source map if generated
320
- if (options.sourcemap) {
321
- const mapSource = resolve(tempDir, 'apex.css.map');
322
- const mapDest = resolve(outputDir, `${filename}.css.map`);
323
- if (existsSync(mapSource)) {
324
- const mapContent = readFileSync(mapSource, 'utf-8');
325
- writeFileSync(mapDest, mapContent);
326
- logger.success('Source map generated');
327
- }
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);
328
329
  }
329
330
 
330
331
  // Output SCSS if requested
331
332
  if (options.format === 'scss' || options.format === 'both') {
332
- writeFileSync(resolve(outputDir, `${filename}.scss`), scssContent);
333
- const scssPath = logger.path(`${filename}.scss`);
334
- logger.success(`Generated: ${scssPath}`);
333
+ const { filename } = getOutputFilenames(layers);
334
+ const scssFilename = `${filename}.scss`;
335
+ writeFileSync(resolve(outputDir, scssFilename), scssContent);
336
+ logger.success(`Generated: ${logger.path(scssFilename)}`);
335
337
  }
336
338
 
337
339
  // Clean up temp directory
@@ -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: answers.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 or CLI)
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
- 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
- }
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('npx apexcss build')} to generate your CSS`,
221
- `Run ${logger.cmd('npx apexcss watch')} during development`
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
- * Prompt for overwrite confirmation
228
- * @returns {Promise<{overwrite: boolean}>}
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} outputDir - Output directory
322
+ * @param {string} [_outputDir] - Output directory (unused - kept for API compatibility)
264
323
  * @returns {string} - Import statement
265
324
  */
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
-
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 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';
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', './dist/')
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, astro)')
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/layout.tsx', 'src/app/layout.jsx', 'app/layout.tsx', 'app/layout.jsx'],
17
- importStatement: 'import \'apexcss\';\n',
18
- cssConfig: '// Import in layout.tsx:\nimport \'apexcss/base\';\nimport \'apexcss/utilities\';\nimport \'apexcss/themes\';\n',
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 framework-specific output directory recommendation
153
- * @param {string} frameworkId - Framework identifier
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(frameworkId) {
157
- const recommendations = {
158
- next: './dist/',
159
- nuxt: './dist/',
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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apexcss-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "ApexCSS CLI - Build and customize your CSS framework",
5
5
  "type": "module",
6
6
  "main": "cli/index.js",
@@ -62,7 +62,7 @@
62
62
  "inquirer": "^9.2.0"
63
63
  },
64
64
  "peerDependencies": {
65
- "apexcss": ">=0.3.0",
65
+ "apexcss": "^0.4.1",
66
66
  "sass": ">=1.90.0",
67
67
  "vite": ">=7.0.0"
68
68
  },