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 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
@@ -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 entryFile = resolve(tempDir, 'apex-entry.scss');
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
- // Get output filenames based on layers
271
- const { filename, description } = getOutputFilenames(layers);
305
+ // Build individual layer files for cascade layer support
306
+ const layerFiles = ['base', 'utilities', 'themes'];
272
307
 
273
- // Write CSS output
274
- const finalCssPath = resolve(outputDir, `${filename}.css`);
275
- writeFileSync(finalCssPath, result.css);
308
+ for (const layer of layerFiles) {
309
+ if (!layers.includes(layer)) {
310
+ continue;
311
+ }
276
312
 
277
- // Calculate file size
278
- const contentBytes = Buffer.byteLength(result.css, 'utf8');
279
- const sizeKB = (contentBytes / 1024).toFixed(2);
313
+ const layerEntry = resolve(tempDir, `${layer}.scss`);
314
+ if (!existsSync(layerEntry)) {
315
+ continue;
316
+ }
280
317
 
281
- const filePath = logger.path(`${filename}.css`);
282
- logger.success(`Built: ${filePath} (${sizeKB} KB) [${description}]`);
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
- // Write source map if generated
285
- if (options.sourcemap && result.sourceMap) {
286
- const mapDest = resolve(outputDir, `${filename}.css.map`);
287
- writeFileSync(mapDest, JSON.stringify(result.sourceMap));
288
- logger.success('Source map generated');
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
- writeFileSync(resolve(outputDir, `${filename}.scss`), scssContent);
294
- const scssPath = logger.path(`${filename}.scss`);
295
- 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)}`);
296
337
  }
297
338
 
298
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.1",
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",