diff --git a/README.md b/README.md index a9cdd7f..e526a01 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Razerspine Stack is designed for developers who want: Scaffolds production-ready projects with flexible architecture - ⚙️ **Build System (`@razerspine/build`)** - Modular webpack configuration with smart defaults + Modular webpack configuration with smart defaults via the `defineConfig` API - 🎨 **UI Layer (`@razerspine/ui`)** Pug-based UI toolkit and reusable components @@ -360,6 +360,32 @@ Removes: - `dist` - `lock files` +### Webpack configuration + +All templates use the `defineConfig` API from `@razerspine/build`: + +```js +const { defineConfig } = require('@razerspine/build'); +const uiKit = require('@razerspine/ui'); + +module.exports = defineConfig({ + scripts: 'ts', + styles: 'scss', + appType: 'spa', + templates: { + type: 'pug', + entry: 'src/app/app.pug', + }, + resolve: { + alias: { + 'pug-mixins': uiKit.paths.mixins, + }, + }, +}); +``` + +`defineConfig` returns a Webpack-compatible factory function and handles dev/prod branching internally. + --- ## Release Process diff --git a/docs/architecture.md b/docs/architecture.md index d4284f8..c00b325 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -238,15 +238,20 @@ No: ```ts defineConfig({ + scripts: 'ts', + styles: 'scss', appType: 'spa', - script: 'ts', - style: 'scss', templates: { - entry: 'src/app/app.pug' - } + type: 'pug', + entry: 'src/app/app.pug', + }, }); ``` +`defineConfig` accepts a static object, a synchronous factory `(env) => config`, or an async factory. +It always returns a Webpack-compatible factory function — mode is resolved from `config.mode` → +CLI `--mode` → `'development'` (default). + ### Pug Dual Mode | Mode | Purpose | diff --git a/docs/release-checklist.md b/docs/release-checklist.md index b55ceb5..32d3571 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -131,6 +131,7 @@ If releasing `@razerspine/create-app`: - `@razerspine/runtime` - `@razerspine/build` - `@razerspine/ui` +- [ ] All template `webpack.config.js` files use `defineConfig` API (not `createBaseConfig` / `createDevConfig` / `createProdConfig`) - [ ] `--pm` flag works (`npm`, `pnpm`, `yarn`, `bun`) - [ ] `--dry-run` works correctly - [ ] `--no-install` works correctly diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 35eca4d..c337eb7 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,12 +1,99 @@ # Changelog +## [1.0.2] - 2026-04-09 + +### Added + +- **`.gitignore` generation** (`steps/write-gitignore.step.ts`, `utils/write-gitignore.ts`): the pipeline now + automatically writes a `.gitignore` file into every generated project. The file covers `node_modules/`, `dist/`, + log files, `.env*` variants, common editor directories, and OS metadata files. The step runs after `patchPackageStep` + and before dependency installation; it is a no-op in `--dry-run` mode. + +### Templates + +#### Updated + +- **Dependency versions** — all 8 templates updated to the latest published versions of core packages: + - `@razerspine/build` → `^1.0.2` + - `@razerspine/runtime` → `^1.0.2` + - `@razerspine/ui` → `^1.0.2` + +- **Webpack config migrated to `defineConfig` API** — all 8 templates (`spa-pug-scss-ts`, + `spa-pug-scss-js`, `spa-pug-less-ts`, `spa-pug-less-js`, `mpa-pug-scss-ts`, `mpa-pug-scss-js`, + `mpa-pug-less-ts`, `mpa-pug-less-js`) have been refactored from the low-level + `createBaseConfig` / `createDevConfig` / `createProdConfig` pattern to the high-level `defineConfig` + helper exposed by `@razerspine/build`. Manual mode resolution (`argv?.mode || env?.mode || …`) and the + dev/prod branching `if` block are fully removed — `defineConfig` handles this internally and returns a + Webpack-compatible factory function. The `templates` object now explicitly declares `type: 'pug'`. + +#### Fixed + +- **`@icons` alias path in SPA TypeScript `tsconfig.json`** (`spa-pug-scss-ts`, `spa-pug-less-ts`): the + path mapping was `"assets/icons*"` (missing trailing slash), which caused TypeScript to fail to resolve + the alias. Corrected to `"assets/icons/*"`. + +- **Missing `tsconfig.json` compiler options in SPA TypeScript templates** (`spa-pug-scss-ts`, + `spa-pug-less-ts`): `"isolatedModules": true` and `"sourceMap": true` were present in MPA templates but + absent in their SPA counterparts. Both options are now aligned across all TypeScript templates. + +#### Removed + +- **`dotenv`** removed from `devDependencies` in all 8 templates — the package was never imported or used + in any webpack config or source file. + +- **`webpack-merge`** removed from `devDependencies` in all 8 templates — manual config merging was + previously required with the low-level API. With `defineConfig`, all merging is handled internally by + `@razerspine/build`. + +### Fixed + +- **Entry point error handling** (`src/index.ts`): replaced `.then()` (no-op) with `.catch()` — ensures top-level fatal + errors are never silently swallowed. + +- **`packageManager` field in generated `package.json`** (`utils/patch-package.ts`): field was set to `"${pm}@latest"`, + which is invalid for corepack. It now uses `execSync("${pm} --version")` to inject the exact installed version (e.g. + `pnpm@9.1.0`). If version detection fails the field is skipped with a warning instead of writing a broken value. + +- **`bun.lock` not filtered during template copy** (`utils/copier.ts`): `IGNORED_FILES` contained `bun.lockb` but not + `bun.lock` (Bun 1.2+ format). The new entry is now included so lock files are never copied into generated projects. + +- **Signal-terminated install process** (`utils/installer.ts`): `close` event handler did not distinguish between a + normal exit (`code !== 0`) and signal termination (`code === null`). The error message now correctly reports + `"terminated by signal SIGTERM"` instead of the misleading `"exit code null"`. + +- **`JSON.parse()` without error handling** (`templates/template-loader.ts`, `utils/patch-package.ts`): bare + `JSON.parse()` calls would throw a native `SyntaxError` with a raw stack trace on malformed files. Both are now + wrapped in `try/catch` and surface a descriptive error message with the file path. + +- **`validateRawArgs` skipped multi-argument inputs** (`cli/validate-args.ts`): the early-return guard + `if (rawArgs.length !== 1) return` caused the reserved-word check to be bypassed whenever the user passed zero or + multiple positional arguments. The function now iterates over every argument regardless of count. + +### Changed + +- **Windows install process** (`utils/installer.ts`): replaced `shell: true` (which routes through `cmd.exe` and is a + potential injection surface) with `shell: false` + `cmd.cmd` suffix resolution. Package managers are now invoked + directly as `npm.cmd`, `pnpm.cmd`, etc., matching the standard cross-platform Node.js convention. + +### Refactored + +- **Eliminated redundant `loadTemplates()` calls** (`cli/resolve-template.ts`, `core/create-app.ts`, `steps/`): + templates were loaded three times per CLI session — once as a module-level side effect in `templates.ts`, and once + each in `cli/resolve-template.ts` and `resolveTemplateStep`. The CLI now creates a single `TemplateService` instance, + resolves and loads the template in one pass, and passes the ready `LoadedTemplate` object directly into the pipeline + context. `resolveTemplateStep` has been removed as a consequence. + +--- + ## [1.0.1] - 2026-03-30 ### Fixed -- **Template Dependencies**: Added `html-webpack-plugin` to `devDependencies` in all templates (`spa-pug-scss-ts`, etc.). This -resolves the `MODULE_NOT_FOUND` error when running `npm run dev` in a newly generated project, caused by the peer dependency -requirements of `@razerspine/build`. +- **Template Dependencies**: Added `html-webpack-plugin` to `devDependencies` in all templates (`spa-pug-scss-ts`, + etc.). This + resolves the `MODULE_NOT_FOUND` error when running `npm run dev` in a newly generated project, caused by the peer + dependency + requirements of `@razerspine/build`. --- diff --git a/packages/create-app/README.md b/packages/create-app/README.md index 22de852..a35f7ca 100644 --- a/packages/create-app/README.md +++ b/packages/create-app/README.md @@ -1,7 +1,7 @@ # @razerspine/create-app [![npm version](https://img.shields.io/npm/v/@razerspine/create-app.svg)](https://www.npmjs.com/package/@razerspine/create-app) -[![Vitest](https://img.shields.io/badge/Vitest-62_passed-success?logo=vitest)]() +[![CI](https://github.com/Razerspine/razerspine-stack/actions/workflows/ci.yml/badge.svg)](https://github.com/Razerspine/razerspine-stack/actions) [![changelog](https://img.shields.io/badge/docs-changelog-blue.svg)](./CHANGELOG.md) [![license](https://img.shields.io/npm/l/@razerspine/create-app.svg)](./LICENSE) @@ -20,7 +20,7 @@ Create a modern webpack project using production-ready **SPA or MPA templates** - [Template Resolution](#template-resolution) - [Testing](#testing) - [How It Works](#how-it-works) -- [Changelog (1.0.0)](#changelog-100) +- [Changelog](#changelog) - [Documentation](#documentation) - [License](#license) @@ -105,7 +105,7 @@ Supported: - Auto script adaptation: - `npm run build` → `pnpm build` / `yarn build` -- Adds `packageManager` field to `package.json` +- Injects exact `packageManager` version into `package.json` (corepack-compatible, e.g. `pnpm@9.1.0`) - Fallback to `npm` if not specified --- @@ -202,83 +202,51 @@ Highlights: ## How It Works -Pipeline-based architecture: +The CLI resolves and loads the template before the pipeline starts, then passes the ready template object directly into +the first step: ```text -resolve template +CLI layer + resolve template (TemplateService — single load) ↓ -prepare directory +Pipeline + prepare directory ↓ -copy files + copy files ↓ -patch package.json + patch package.json ↓ -install dependencies + write .gitignore + ↓ + install dependencies ``` --- -## Changelog (1.0.0) +## Changelog + +### [1.0.2] — Latest -### Major Release +- Added automatic `.gitignore` generation for every scaffolded project (`node_modules/`, `dist/`, `.env*`, logs, editor dirs) +- Fixed `packageManager` field: now injects exact version (e.g. `pnpm@9.1.0`) instead of invalid `@latest` +- Fixed `bun.lock` (Bun 1.2+) being copied into generated projects +- Fixed signal-terminated install process reporting misleading `"exit code null"` +- Fixed `JSON.parse()` in template loader and package patcher — now surfaces file path on error +- Fixed `validateRawArgs` skipping checks for 0 or 2+ positional arguments +- Fixed top-level `run().then()` no-op — replaced with `.catch()` +- Improved Windows install: `shell: false` + `.cmd` suffix instead of `shell: true` +- Refactored template resolution: single `TemplateService` load per session, removed redundant `resolveTemplateStep` + +### [1.0.0] — Major Release - Full CLI rewrite - New package: `@razerspine/create-app` - New command: `create` - ---- - -### ⚠️ Breaking Changes - -- `create-webpack-starter` → `create-app` -- New binary: `dist/index.cjs` -- Switched to `tsup` -- Removed direct `package.json` imports - ---- - -### Features - - Interactive CLI (inquirer) - Smart template resolution -- Dry-run mode -- Improved validation - ---- - -### Package Manager Support - -- Added `--pm` flag -- Supports: npm, pnpm, yarn, bun -- Script auto-adaptation -- Injects `packageManager` field - ---- - -### Testing - -- Vitest migration -- Full E2E coverage -- Fixed cleanup race conditions - ---- - -### Architecture - -- Pipeline-based system -- Clean separation: - - CLI - - core - - steps - - utils - ---- - -### DX Improvements - -- `tsx` instead of `ts-node` -- Better logs (ora + kleur) -- Improved CLI UX +- Dry-run mode, `--pm` flag +- Vitest migration, full E2E coverage +- Pipeline-based architecture --- diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 13653b3..8044fc6 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@razerspine/create-app", - "version": "1.0.1", + "version": "1.0.2", "description": "CLI to scaffold production-ready SPA & MPA applications with a modular build system, Pug templates, and a lightweight reactive runtime.", "author": "Razerspine", "license": "ISC", diff --git a/packages/create-app/src/cli/resolve-template.ts b/packages/create-app/src/cli/resolve-template.ts index a401d27..7a20802 100644 --- a/packages/create-app/src/cli/resolve-template.ts +++ b/packages/create-app/src/cli/resolve-template.ts @@ -1,11 +1,11 @@ -import {TemplateKey} from '../templates/templates'; import {createTemplateService} from '../core/template.service'; -import {TemplateFeatures} from '../templates/types'; +import {TemplateFeatures, LoadedTemplate} from '../templates/types'; /** - * Resolves template key using TemplateService. + * Resolves and returns the full LoadedTemplate using a single TemplateService instance. */ -export function resolveTemplate(input: TemplateFeatures): TemplateKey { +export function resolveTemplate(input: TemplateFeatures): LoadedTemplate { const service = createTemplateService(); - return service.resolve(input); + const key = service.resolve(input); + return service.getByKey(key); } diff --git a/packages/create-app/src/cli/types.ts b/packages/create-app/src/cli/types.ts index 1d105c1..f88edd3 100644 --- a/packages/create-app/src/cli/types.ts +++ b/packages/create-app/src/cli/types.ts @@ -1,5 +1,4 @@ -import {TemplateFeatures, AppType} from '../templates/types'; -import {TemplateKey} from '../templates/templates'; +import {TemplateFeatures, AppType, LoadedTemplate} from '../templates/types'; import {PackageManager} from '../utils'; /** @@ -17,7 +16,7 @@ export type CliOptions = Partial & { */ export type CliContext = { projectName: string; - template: TemplateKey; + template: LoadedTemplate; appType: AppType; noInstall: boolean; dryRun: boolean; diff --git a/packages/create-app/src/cli/validate-args.ts b/packages/create-app/src/cli/validate-args.ts index 3cee6b0..0ee409f 100644 --- a/packages/create-app/src/cli/validate-args.ts +++ b/packages/create-app/src/cli/validate-args.ts @@ -1,24 +1,27 @@ /** * Prevents accidental project creation using reserved words. + * Handled gracefully to redirect users who forget the '--' prefix. + * Checks every positional argument regardless of total count. + * + * @param {string[]} rawArgs - The raw positional arguments passed to the CLI. */ export function validateRawArgs(rawArgs: string[]) { - if (rawArgs.length !== 1) return; + for (const arg of rawArgs) { + const normalized = arg.toLowerCase(); - const arg = rawArgs[0].toLowerCase(); + const isVersion = ['version', 'v'].includes(normalized); + const isHelp = ['help', 'h'].includes(normalized); - if (['version', 'v'].includes(arg)) { - console.error(`⚠️ Error: '${rawArgs[0]}' is not a valid project name.`); - console.error('Did you mean to check the version? Use one of these:'); - console.error(' create --version'); - console.error(' create -v'); - process.exit(1); - } + if (isVersion || isHelp) { + const type = isVersion ? 'version' : 'help'; + const flag = isVersion ? '-v' : '-h'; + const action = isVersion ? 'check the version' : 'ask for help'; - if (['help', 'h'].includes(arg)) { - console.error(`⚠️ Error: '${rawArgs[0]}' is not a valid project name.`); - console.error('Did you mean to ask for help? Use one of these:'); - console.error(' create --help'); - console.error(' create -h'); - process.exit(1); + console.error(`⚠️ Error: '${arg}' is not a valid project name.`); + console.error(`Did you mean to ${action}? Use one of these:`); + console.error(` create --${type}`); + console.error(` create ${flag}`); + process.exit(1); + } } } diff --git a/packages/create-app/src/core/create-app.ts b/packages/create-app/src/core/create-app.ts index 7bb0122..6c6f319 100644 --- a/packages/create-app/src/core/create-app.ts +++ b/packages/create-app/src/core/create-app.ts @@ -2,20 +2,20 @@ import path from 'path'; import ora from 'ora'; import {Pipeline} from './pipeline'; import { - resolveTemplateStep, prepareDirectoryStep, copyTemplateStep, installDepsStep, - patchPackageStep + patchPackageStep, + writeGitignoreStep } from '../steps'; import {log} from '../utils'; -import {CreateAppOptions, BasePipelineContext} from './types'; +import {CreateAppOptions, TemplateResolvedContext} from './types'; /** * Main orchestration function for project generation. * * Responsibilities: - * - builds initial pipeline context + * - builds initial pipeline context (template already resolved by CLI) * - assembles pipeline using the Builder pattern * - executes pipeline steps sequentially * - patches package.json with project-specific metadata @@ -24,11 +24,13 @@ export async function createApp(options: CreateAppOptions): Promise { const spinner = ora(); /** - * Initial pipeline context (before template is resolved) + * Initial pipeline context — template is already resolved by the CLI layer, + * so the pipeline starts with TemplateResolvedContext directly. */ - const ctx: BasePipelineContext = { + const ctx: TemplateResolvedContext = { projectName: options.projectName, - templateKey: options.templateKey, + templateKey: options.template.key, + template: options.template, appType: options.appType, targetDir: path.resolve(process.cwd(), options.projectName), noInstall: Boolean(options.noInstall), @@ -39,33 +41,17 @@ export async function createApp(options: CreateAppOptions): Promise { /** * Execute pipeline with strict type flow. * - * Type transitions: - * 1. create() - * -> initial context - * - * 2. resolveTemplateStep - * BasePipelineContext -> TemplateResolvedContext - * - * 3. prepareDirectoryStep - * TemplateResolvedContext -> TemplateResolvedContext - * - * 4. copyTemplateStep - * TemplateResolvedContext -> TemplateResolvedContext - * - * 5. patchPackageStep - * TemplateResolvedContext -> TemplateResolvedContext - * - * 6. installDepsStep - * TemplateResolvedContext -> TemplateResolvedContext - * - * Final result: - * Promise + * 1. prepareDirectoryStep — TemplateResolvedContext -> TemplateResolvedContext + * 2. copyTemplateStep — TemplateResolvedContext -> TemplateResolvedContext + * 3. patchPackageStep — TemplateResolvedContext -> TemplateResolvedContext + * 4. writeGitignoreStep — TemplateResolvedContext -> TemplateResolvedContext + * 5. installDepsStep — TemplateResolvedContext -> TemplateResolvedContext */ - await Pipeline.create() - .addStep(resolveTemplateStep) + await Pipeline.create() .addStep(prepareDirectoryStep(spinner)) .addStep(copyTemplateStep(spinner)) .addStep(patchPackageStep(spinner)) + .addStep(writeGitignoreStep(spinner)) .addStep(installDepsStep(spinner)) .run(ctx); diff --git a/packages/create-app/src/core/template.service.ts b/packages/create-app/src/core/template.service.ts index 7305f3c..da62c19 100644 --- a/packages/create-app/src/core/template.service.ts +++ b/packages/create-app/src/core/template.service.ts @@ -1,6 +1,6 @@ import {loadTemplates} from '../templates/template-loader'; import {resolveTemplateKey} from '../templates/template-resolver'; -import {TemplateKey} from '../templates/templates'; +import {TemplateKey} from '../templates/types'; import {LoadedTemplate, TemplateFeatures} from '../templates/types'; import {getTemplatesPath} from '../utils'; diff --git a/packages/create-app/src/core/types.ts b/packages/create-app/src/core/types.ts index 5902ca9..ddb62a6 100644 --- a/packages/create-app/src/core/types.ts +++ b/packages/create-app/src/core/types.ts @@ -6,7 +6,7 @@ import {PackageManager} from '../utils'; */ export type CreateAppOptions = { projectName: string; - templateKey: string; + template: LoadedTemplate; appType: AppType; noInstall?: boolean; dryRun?: boolean; diff --git a/packages/create-app/src/index.ts b/packages/create-app/src/index.ts index 7beaac7..4eff964 100644 --- a/packages/create-app/src/index.ts +++ b/packages/create-app/src/index.ts @@ -2,21 +2,39 @@ import {getCliContext} from './cli'; import {createApp} from './core/create-app'; import {log} from './utils'; +/** + * Global handler for unhandled promise rejections. + * Specifically catches intentional user cancellations (e.g., exiting prompts). + * Logs unexpected errors to prevent silent failures. + * + * @param err - The unhandled error object + */ process.on('unhandledRejection', (err: unknown) => { - const error = err as { isTtyError?: boolean; name?: string }; + const error = err as { isTtyError?: boolean; name?: string; message?: string }; + + // Handle user cancellation gracefully if (error?.isTtyError || error?.name === 'ExitPromptError') { log.info('Cancelled by user'); - process.exit(130); + process.exit(130); // 130 is the standard exit code for script termination via Ctrl+C + } else { + // Fallback for unexpected unhandled rejections + log.error(`❌ Unhandled Rejection: ${error?.message || err}`); + process.exit(1); } }); -async function run() { +/** + * Main CLI application entry point. + * Initializes context and triggers the application scaffolding process. + * * @returns Promise resolving when execution finishes + */ +async function run(): Promise { try { const cli = await getCliContext(); await createApp({ projectName: cli.projectName, - templateKey: cli.template, + template: cli.template, appType: cli.appType, noInstall: cli.noInstall, dryRun: cli.dryRun, @@ -24,9 +42,14 @@ async function run() { }); } catch (err: unknown) { const error = err as { message?: string }; - console.error('❌ Error:', error?.message || err); + // Use custom logger instead of native console.error for consistency + log.error(`❌ Error: ${error?.message || err}`); process.exit(1); } } -run().then(); +// Execute the main function and ensure any catastrophic top-level errors are caught +run().catch((err: unknown) => { + log.error(`❌ Fatal Error: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); +}); diff --git a/packages/create-app/src/steps/copy-template.step.ts b/packages/create-app/src/steps/copy-template.step.ts index 8669dfb..f9acd79 100644 --- a/packages/create-app/src/steps/copy-template.step.ts +++ b/packages/create-app/src/steps/copy-template.step.ts @@ -5,7 +5,10 @@ import {copyTemplate} from '../utils'; /** * Copies template files from the source directory to the project target directory. + * Includes error handling to ensure the UI spinner fails gracefully on file system errors. * * Requirement: Needs TemplateResolvedContext to access ctx.template. + * @param {ora.Ora} spinner - The CLI spinner instance for UI feedback. + * @returns {PipelineStep} The pipeline step function. */ export const copyTemplateStep = ( spinner: ora.Ora @@ -16,9 +19,14 @@ export const copyTemplateStep = ( return ctx; } - spinner.start('Copying template...'); - await copyTemplate(ctx.template.filesPath, ctx.targetDir); - spinner.succeed('Template copied'); + try { + spinner.start('Copying template...'); + await copyTemplate(ctx.template.filesPath, ctx.targetDir); + spinner.succeed('Template copied'); + } catch (error) { + spinner.fail('Failed to copy template'); + throw error; + } return ctx; }; diff --git a/packages/create-app/src/steps/index.ts b/packages/create-app/src/steps/index.ts index 7cfd1b4..2ac174f 100644 --- a/packages/create-app/src/steps/index.ts +++ b/packages/create-app/src/steps/index.ts @@ -1,5 +1,5 @@ export {copyTemplateStep} from './copy-template.step'; export {installDepsStep} from './install-deps.step'; export {prepareDirectoryStep} from './prepare-directory.step'; -export {resolveTemplateStep} from './resolve-template.step'; export {patchPackageStep} from './patch-package.step'; +export {writeGitignoreStep} from './write-gitignore.step'; diff --git a/packages/create-app/src/steps/install-deps.step.ts b/packages/create-app/src/steps/install-deps.step.ts index 4a21f19..b595f7d 100644 --- a/packages/create-app/src/steps/install-deps.step.ts +++ b/packages/create-app/src/steps/install-deps.step.ts @@ -5,7 +5,10 @@ import {installDeps} from '../utils'; /** * Installs project dependencies using the selected or detected package manager. + * Handles potential installation failures and updates the UI spinner accordingly. * * Note: Should be executed after the template files are successfully copied. + * @param {ora.Ora} spinner - The CLI spinner instance for UI feedback. + * @returns {PipelineStep} The pipeline step function. */ export const installDepsStep = ( spinner: ora.Ora @@ -21,12 +24,15 @@ export const installDepsStep = ( return ctx; } - spinner.start('Installing dependencies...'); - - // Pass ctx.pm as the override if the user explicitly selected a package manager via CLI - await installDeps(ctx.targetDir, ctx.pm); - - spinner.succeed('Dependencies installed'); + try { + spinner.start('Installing dependencies...'); + // Pass ctx.pm as the override if the user explicitly selected a package manager via CLI + await installDeps(ctx.targetDir, ctx.pm); + spinner.succeed('Dependencies installed'); + } catch (error) { + spinner.fail('Failed to install dependencies'); + throw error; + } return ctx; }; diff --git a/packages/create-app/src/steps/prepare-directory.step.ts b/packages/create-app/src/steps/prepare-directory.step.ts index 5367b81..ffe7e2e 100644 --- a/packages/create-app/src/steps/prepare-directory.step.ts +++ b/packages/create-app/src/steps/prepare-directory.step.ts @@ -8,8 +8,11 @@ import {log} from '../utils'; /** * Ensures target directory is ready for project generation. * Handles overwrite confirmation if the directory already exists. + * Gracefully handles errors during directory removal. * * Note: This step is generic to preserve any existing context extensions * (like resolved templates) while only requiring BasePipelineContext fields. + * @param {ora.Ora} spinner - The CLI spinner instance for UI feedback. + * @returns {PipelineStep} The pipeline step function. */ export const prepareDirectoryStep = ( spinner: ora.Ora @@ -39,9 +42,14 @@ export const prepareDirectoryStep = ( process.exit(0); } - spinner.start('Cleaning target directory...'); - await fs.remove(ctx.targetDir); - spinner.succeed('Directory cleaned'); + try { + spinner.start('Cleaning target directory...'); + await fs.remove(ctx.targetDir); + spinner.succeed('Directory cleaned'); + } catch (error) { + spinner.fail('Failed to clean target directory'); + throw error; + } } return ctx; diff --git a/packages/create-app/src/steps/resolve-template.step.ts b/packages/create-app/src/steps/resolve-template.step.ts deleted file mode 100644 index e1297fb..0000000 --- a/packages/create-app/src/steps/resolve-template.step.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {PipelineStep} from '../core/pipeline'; -import {BasePipelineContext, TemplateResolvedContext} from '../core/types'; -import {createTemplateService} from '../core/template.service'; - -/** - * Resolves the requested template and enriches the pipeline context. - * * Output: TemplateResolvedContext (Base + template metadata) - */ -export const resolveTemplateStep: PipelineStep< - BasePipelineContext, - TemplateResolvedContext -> = async (ctx) => { - const service = createTemplateService(); - const template = service.getByKey(ctx.templateKey); - - return { - ...ctx, - template - }; -}; diff --git a/packages/create-app/src/steps/write-gitignore.step.ts b/packages/create-app/src/steps/write-gitignore.step.ts new file mode 100644 index 0000000..485dcd6 --- /dev/null +++ b/packages/create-app/src/steps/write-gitignore.step.ts @@ -0,0 +1,32 @@ +import ora from 'ora'; +import {PipelineStep} from '../core/pipeline'; +import {TemplateResolvedContext} from '../core/types'; +import {writeGitignore} from '../utils'; + +/** + * Writes a .gitignore file to the target project directory. + * + * Execution order: + * - MUST run after template copy (target directory must exist) + */ +export const writeGitignoreStep = ( + spinner: ora.Ora +): PipelineStep => { + return async (ctx) => { + if (ctx.dryRun) { + spinner.info('[dry-run] .gitignore would be written'); + return ctx; + } + + try { + spinner.start('Writing .gitignore...'); + await writeGitignore(ctx.targetDir); + spinner.succeed('.gitignore written'); + } catch (error) { + spinner.fail('Failed to write .gitignore'); + throw error; + } + + return ctx; + }; +}; diff --git a/packages/create-app/src/templates/template-loader.ts b/packages/create-app/src/templates/template-loader.ts index 3378c5d..e31e996 100644 --- a/packages/create-app/src/templates/template-loader.ts +++ b/packages/create-app/src/templates/template-loader.ts @@ -4,12 +4,20 @@ import {LoadedTemplate, TemplateMeta} from './types'; /** * Safely reads and parses JSON file. + * Includes error handling to provide meaningful feedback on parse failures. * * @param filePath - absolute path to JSON file + * @returns parsed JSON content of type T + * @throws Error if file cannot be read or JSON is invalid */ function readJson(filePath: string): T { - const content = fs.readFileSync(filePath, 'utf-8'); - return JSON.parse(content) as T; + try { + const content = fs.readFileSync(filePath, 'utf-8'); + return JSON.parse(content) as T; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to parse JSON configuration at ${filePath}: ${errorMessage}`); + } } /** diff --git a/packages/create-app/src/templates/template-resolver.ts b/packages/create-app/src/templates/template-resolver.ts index d708336..eaef3f2 100644 --- a/packages/create-app/src/templates/template-resolver.ts +++ b/packages/create-app/src/templates/template-resolver.ts @@ -3,10 +3,11 @@ import {ResolveInput, LoadedTemplate} from './types'; /** * Resolves template key based on feature flags. + * Optimized condition checking for better readability. * * @param templates - available templates map * @param input - feature selection - * @returns matching template key or null + * @returns matching template key or null if no match is found */ export function resolveTemplateKey( templates: Record, @@ -19,9 +20,12 @@ export function resolveTemplateKey( for (const [key, template] of Object.entries(templates)) { const features = template.meta.features; - if (!features) continue; - - if (features.appType === appType && features.style === style && features.script === script) { + if ( + features && + features.appType === appType && + features.style === style && + features.script === script + ) { return key as TemplateKey; } } diff --git a/packages/create-app/src/templates/types.ts b/packages/create-app/src/templates/types.ts index b4aa34a..2363084 100644 --- a/packages/create-app/src/templates/types.ts +++ b/packages/create-app/src/templates/types.ts @@ -2,6 +2,11 @@ * Atomic technology types supported by our templates. */ export type AppType = 'mpa' | 'spa'; + +/** + * Template key type (string-based, derived from filesystem directory name). + */ +export type TemplateKey = string; export type StyleType = 'scss' | 'less'; export type ScriptType = 'js' | 'ts'; diff --git a/packages/create-app/src/utils/copier.ts b/packages/create-app/src/utils/copier.ts index 2a5e5e2..3d2bf28 100644 --- a/packages/create-app/src/utils/copier.ts +++ b/packages/create-app/src/utils/copier.ts @@ -1,5 +1,5 @@ +import path from 'node:path'; import fs from 'fs-extra'; -import path from 'path'; const IGNORED_DIRS = [ 'node_modules', @@ -10,7 +10,8 @@ const IGNORED_FILES = [ 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', - 'bun.lockb' + 'bun.lockb', + 'bun.lock' ]; /** diff --git a/packages/create-app/src/utils/get-templates-path.ts b/packages/create-app/src/utils/get-templates-path.ts index 85d2e48..c7eec15 100644 --- a/packages/create-app/src/utils/get-templates-path.ts +++ b/packages/create-app/src/utils/get-templates-path.ts @@ -1,22 +1,22 @@ -import path from 'path'; +import path from 'node:path'; /** * Gets the absolute path to the templates directory ('templates' folder in the package root). * Automatically adjusts based on the environment (development or compiled package). + * + * Detection strategy: checks if __dirname ends with 'dist' (compiled output via tsup). + * - Production (dist/): __dirname = {pkg-root}/dist → templates is one level up + * - Development (src/utils/): __dirname = {pkg-root}/src/utils → templates is two levels up + * * @returns {string} Absolute path to the templates' directory. */ export function getTemplatesPath(): string { - // If the execution path ends with 'dist' (or '\dist' on Windows), - // it means this is a production build (e.g., running via npx) const isProd = __dirname.endsWith('dist') || __dirname.endsWith(path.sep + 'dist'); if (isProd) { - // Relative to dist/index.cjs, the templates folder is one level up return path.resolve(__dirname, '../templates'); } - // For development mode (assuming this helper is located in, e.g., src/utils/get-templates-path.ts) - // We go up two levels from src/utils to the package root return path.resolve(__dirname, '../../templates'); } diff --git a/packages/create-app/src/utils/index.ts b/packages/create-app/src/utils/index.ts index 9f7e686..f826171 100644 --- a/packages/create-app/src/utils/index.ts +++ b/packages/create-app/src/utils/index.ts @@ -1,6 +1,7 @@ export {copyTemplate} from './copier'; export {installDeps} from './installer'; export {patchPackageJson} from './patch-package'; +export {writeGitignore} from './write-gitignore'; export {getTemplatesPath} from './get-templates-path'; export {log} from './logger'; export {VERSION} from './package-version'; diff --git a/packages/create-app/src/utils/installer.ts b/packages/create-app/src/utils/installer.ts index d6115ae..37d5782 100644 --- a/packages/create-app/src/utils/installer.ts +++ b/packages/create-app/src/utils/installer.ts @@ -89,10 +89,11 @@ export function installDeps( console.log(`📦 Installing dependencies using ${pm}...`); return new Promise((resolve, reject) => { - const child = spawn(cmd, args, { + const isWindows = process.platform === 'win32'; + const child = spawn(isWindows ? `${cmd}.cmd` : cmd, args, { cwd, stdio: 'inherit', - shell: process.platform === 'win32', + shell: false, }); child.on('error', (err) => { @@ -103,13 +104,13 @@ export function installDeps( ); }); - child.on('close', (code) => { + child.on('close', (code, signal) => { if (code === 0) { resolve(); + } else if (code === null) { + reject(new Error(`${pm} install was terminated by signal ${signal}`)); } else { - reject( - new Error(`${pm} install failed with exit code ${code}`) - ); + reject(new Error(`${pm} install failed with exit code ${code}`)); } }); }); diff --git a/packages/create-app/src/utils/logger.ts b/packages/create-app/src/utils/logger.ts index b51f8f3..98b999a 100644 --- a/packages/create-app/src/utils/logger.ts +++ b/packages/create-app/src/utils/logger.ts @@ -1,7 +1,15 @@ import kleur from 'kleur'; +/** + * Standardized console logger utility with colorized output. + */ export const log = { - info: (msg: string) => console.log(kleur.cyan(msg)), - success: (msg: string) => console.log(kleur.green(msg)), - error: (msg: string) => console.error(kleur.red(msg)) + /** Logs an informational message in cyan color. */ + info: (msg: string) => console.log(kleur.cyan(msg)), + + /** Logs a success message in green color. */ + success: (msg: string) => console.log(kleur.green(msg)), + + /** Logs an error message in red color to stderr. */ + error: (msg: string) => console.error(kleur.red(msg)) }; diff --git a/packages/create-app/src/utils/package-version.ts b/packages/create-app/src/utils/package-version.ts index 96581f2..0e37e06 100644 --- a/packages/create-app/src/utils/package-version.ts +++ b/packages/create-app/src/utils/package-version.ts @@ -2,4 +2,8 @@ import pkg from '../../package.json'; declare const __VERSION__: string | undefined; +/** + * The current version of the package. + * Falls back to the version from package.json if __VERSION__ global is not injected by the bundler. + */ export const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : pkg.version; diff --git a/packages/create-app/src/utils/patch-package.ts b/packages/create-app/src/utils/patch-package.ts index fa6f50a..80967fe 100644 --- a/packages/create-app/src/utils/patch-package.ts +++ b/packages/create-app/src/utils/patch-package.ts @@ -1,5 +1,6 @@ import fs from 'node:fs/promises'; import path from 'node:path'; +import {execSync} from 'node:child_process'; import {PackageManager} from './types'; /** @@ -7,6 +8,12 @@ import {PackageManager} from './types'; * - sets project name * - adapts scripts to selected package manager * - injects packageManager field + * * Includes error handling to ensure safe file reading and JSON parsing. + * + * @param targetDir - The path to the destination directory. + * @param projectName - The name of the project to set. + * @param pm - The selected package manager. + * @throws Error if package.json cannot be read or parsed. */ export async function patchPackageJson( targetDir: string, @@ -15,36 +22,46 @@ export async function patchPackageJson( ): Promise { const pkgPath = path.join(targetDir, 'package.json'); - const content = await fs.readFile(pkgPath, 'utf-8'); - const pkg = JSON.parse(content); + try { + const content = await fs.readFile(pkgPath, 'utf-8'); + const pkg = JSON.parse(content); - // name - pkg.name = projectName; + // name + pkg.name = projectName; - // prevent accidental publish - pkg.private = true; + // prevent accidental publish + pkg.private = true; - // package manager metadata - pkg.packageManager = `${pm}@latest`; + // package manager metadata + try { + const pmVersion = execSync(`${pm} --version`).toString().trim(); + pkg.packageManager = `${pm}@${pmVersion}`; + } catch (e) { + console.warn(`⚠️ Could not determine ${pm} version, skipping packageManager field.`); + } - const runPrefixes: Record = { - npm: 'npm run ', - yarn: 'yarn ', - pnpm: 'pnpm ', - bun: 'bun run ', - }; + const runPrefixes: Record = { + npm: 'npm run ', + yarn: 'yarn ', + pnpm: 'pnpm ', + bun: 'bun run ', + }; - const newPrefix = runPrefixes[pm]; + const newPrefix = runPrefixes[pm]; - if (pkg.scripts) { - for (const key of Object.keys(pkg.scripts)) { - const command = pkg.scripts[key]; + if (pkg.scripts) { + for (const key of Object.keys(pkg.scripts)) { + const command = pkg.scripts[key]; - if (typeof command === 'string') { - pkg.scripts[key] = command.replace(/\bnpm run\s+/g, newPrefix); + if (typeof command === 'string') { + pkg.scripts[key] = command.replace(/\bnpm run\s+/g, newPrefix); + } } } - } - await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to patch package.json in ${targetDir}: ${errorMessage}`); + } } diff --git a/packages/create-app/src/utils/write-gitignore.ts b/packages/create-app/src/utils/write-gitignore.ts new file mode 100644 index 0000000..c0ce61c --- /dev/null +++ b/packages/create-app/src/utils/write-gitignore.ts @@ -0,0 +1,44 @@ +import path from 'node:path'; +import fs from 'fs-extra'; + +const GITIGNORE_CONTENT = `# dependencies +node_modules/ + +# build output +dist/ + +# logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment +.env +.env.local +.env.*.local + +# editor +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# OS +.DS_Store +Thumbs.db +`; + +/** + * Writes a .gitignore file to the target directory. + * + * @param {string} targetDir - The path to the destination directory. + * @returns {Promise} A promise that resolves when the write operation completes. + */ +export async function writeGitignore(targetDir: string): Promise { + await fs.writeFile(path.join(targetDir, '.gitignore'), GITIGNORE_CONTENT, 'utf-8'); +} diff --git a/packages/create-app/templates/mpa-pug-less-js/files/package.json b/packages/create-app/templates/mpa-pug-less-js/files/package.json index ec23284..7283423 100644 --- a/packages/create-app/templates/mpa-pug-less-js/files/package.json +++ b/packages/create-app/templates/mpa-pug-less-js/files/package.json @@ -16,12 +16,11 @@ "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-env": "^7.28.5", - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "babel-loader": "^10.0.0", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "less": "^4.5.1", @@ -33,12 +32,11 @@ "pug-plugin": "^6.1.0", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0", + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2", "core-js": "^3.48.0" } } diff --git a/packages/create-app/templates/mpa-pug-less-js/files/src/assets/images/logo.svg b/packages/create-app/templates/mpa-pug-less-js/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/mpa-pug-less-js/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/mpa-pug-less-js/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/mpa-pug-less-js/files/src/styles/main.less b/packages/create-app/templates/mpa-pug-less-js/files/src/styles/main.less index d4b6fb7..b8255d5 100644 --- a/packages/create-app/templates/mpa-pug-less-js/files/src/styles/main.less +++ b/packages/create-app/templates/mpa-pug-less-js/files/src/styles/main.less @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/mpa-pug-less-js/files/src/views/layout/_header.pug b/packages/create-app/templates/mpa-pug-less-js/files/src/views/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/mpa-pug-less-js/files/src/views/layout/_header.pug +++ b/packages/create-app/templates/mpa-pug-less-js/files/src/views/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/mpa-pug-less-js/files/webpack.config.js b/packages/create-app/templates/mpa-pug-less-js/files/webpack.config.js index f6cf708..f581e2c 100644 --- a/packages/create-app/templates/mpa-pug-less-js/files/webpack.config.js +++ b/packages/create-app/templates/mpa-pug-less-js/files/webpack.config.js @@ -1,37 +1,23 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'js', - styles: 'less', - appType: 'mpa', - templates: { - entry: 'src/views/pages/', - }, - resolve: { - alias: { - '@views': path.resolve(process.cwd(), 'src/views'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@scripts': path.resolve(process.cwd(), 'src/scripts'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'js', + styles: 'less', + appType: 'mpa', + templates: { + type: 'pug', + entry: 'src/views/pages/', + }, + resolve: { + alias: { + '@views': path.resolve(process.cwd(), 'src/views'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@scripts': path.resolve(process.cwd(), 'src/scripts'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/mpa-pug-less-ts/files/package.json b/packages/create-app/templates/mpa-pug-less-ts/files/package.json index 213d485..08536c8 100644 --- a/packages/create-app/templates/mpa-pug-less-ts/files/package.json +++ b/packages/create-app/templates/mpa-pug-less-ts/files/package.json @@ -14,11 +14,10 @@ "test:build:cloudflare": "CF_PAGES=true npm run build" }, "devDependencies": { - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "less": "^4.5.1", @@ -32,11 +31,10 @@ "typescript": "^5.9.3", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0" + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2" } } diff --git a/packages/create-app/templates/mpa-pug-less-ts/files/src/assets/images/logo.svg b/packages/create-app/templates/mpa-pug-less-ts/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/mpa-pug-less-ts/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/mpa-pug-less-ts/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/mpa-pug-less-ts/files/src/styles/main.less b/packages/create-app/templates/mpa-pug-less-ts/files/src/styles/main.less index d4b6fb7..b8255d5 100644 --- a/packages/create-app/templates/mpa-pug-less-ts/files/src/styles/main.less +++ b/packages/create-app/templates/mpa-pug-less-ts/files/src/styles/main.less @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/mpa-pug-less-ts/files/src/views/layout/_header.pug b/packages/create-app/templates/mpa-pug-less-ts/files/src/views/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/mpa-pug-less-ts/files/src/views/layout/_header.pug +++ b/packages/create-app/templates/mpa-pug-less-ts/files/src/views/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/mpa-pug-less-ts/files/webpack.config.js b/packages/create-app/templates/mpa-pug-less-ts/files/webpack.config.js index 2db74d9..53187f8 100644 --- a/packages/create-app/templates/mpa-pug-less-ts/files/webpack.config.js +++ b/packages/create-app/templates/mpa-pug-less-ts/files/webpack.config.js @@ -1,37 +1,23 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'ts', - styles: 'less', - appType: 'mpa', - templates: { - entry: 'src/views/pages/', - }, - resolve: { - alias: { - '@views': path.resolve(process.cwd(), 'src/views'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@scripts': path.resolve(process.cwd(), 'src/scripts'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'ts', + styles: 'less', + appType: 'mpa', + templates: { + type: 'pug', + entry: 'src/views/pages/', + }, + resolve: { + alias: { + '@views': path.resolve(process.cwd(), 'src/views'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@scripts': path.resolve(process.cwd(), 'src/scripts'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/mpa-pug-scss-js/files/package.json b/packages/create-app/templates/mpa-pug-scss-js/files/package.json index 997f02f..7e76731 100644 --- a/packages/create-app/templates/mpa-pug-scss-js/files/package.json +++ b/packages/create-app/templates/mpa-pug-scss-js/files/package.json @@ -16,12 +16,11 @@ "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-env": "^7.28.5", - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "babel-loader": "^10.0.0", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "parse5": "^7.1.2", @@ -33,12 +32,11 @@ "sass-loader": "^14.1.1", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0", + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2", "core-js": "^3.48.0" } } diff --git a/packages/create-app/templates/mpa-pug-scss-js/files/src/assets/images/logo.svg b/packages/create-app/templates/mpa-pug-scss-js/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/mpa-pug-scss-js/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/mpa-pug-scss-js/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/mpa-pug-scss-js/files/src/styles/main.scss b/packages/create-app/templates/mpa-pug-scss-js/files/src/styles/main.scss index 932bf4b..9977056 100644 --- a/packages/create-app/templates/mpa-pug-scss-js/files/src/styles/main.scss +++ b/packages/create-app/templates/mpa-pug-scss-js/files/src/styles/main.scss @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/mpa-pug-scss-js/files/src/views/layout/_header.pug b/packages/create-app/templates/mpa-pug-scss-js/files/src/views/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/mpa-pug-scss-js/files/src/views/layout/_header.pug +++ b/packages/create-app/templates/mpa-pug-scss-js/files/src/views/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/mpa-pug-scss-js/files/webpack.config.js b/packages/create-app/templates/mpa-pug-scss-js/files/webpack.config.js index 0a6ecaa..974c57b 100644 --- a/packages/create-app/templates/mpa-pug-scss-js/files/webpack.config.js +++ b/packages/create-app/templates/mpa-pug-scss-js/files/webpack.config.js @@ -1,37 +1,23 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'js', - styles: 'scss', - appType: 'mpa', - templates: { - entry: 'src/views/pages/', - }, - resolve: { - alias: { - '@views': path.resolve(process.cwd(), 'src/views'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@scripts': path.resolve(process.cwd(), 'src/scripts'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'js', + styles: 'scss', + appType: 'mpa', + templates: { + type: 'pug', + entry: 'src/views/pages/', + }, + resolve: { + alias: { + '@views': path.resolve(process.cwd(), 'src/views'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@scripts': path.resolve(process.cwd(), 'src/scripts'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/mpa-pug-scss-ts/files/package.json b/packages/create-app/templates/mpa-pug-scss-ts/files/package.json index 770ce6e..1de1d6e 100644 --- a/packages/create-app/templates/mpa-pug-scss-ts/files/package.json +++ b/packages/create-app/templates/mpa-pug-scss-ts/files/package.json @@ -14,11 +14,10 @@ "test:build:cloudflare": "CF_PAGES=true npm run build" }, "devDependencies": { - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "parse5": "^7.1.2", @@ -32,11 +31,10 @@ "typescript": "^5.9.3", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0" + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2" } } diff --git a/packages/create-app/templates/mpa-pug-scss-ts/files/src/assets/images/logo.svg b/packages/create-app/templates/mpa-pug-scss-ts/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/mpa-pug-scss-ts/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/mpa-pug-scss-ts/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/mpa-pug-scss-ts/files/src/styles/main.scss b/packages/create-app/templates/mpa-pug-scss-ts/files/src/styles/main.scss index 932bf4b..9977056 100644 --- a/packages/create-app/templates/mpa-pug-scss-ts/files/src/styles/main.scss +++ b/packages/create-app/templates/mpa-pug-scss-ts/files/src/styles/main.scss @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/mpa-pug-scss-ts/files/src/views/layout/_header.pug b/packages/create-app/templates/mpa-pug-scss-ts/files/src/views/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/mpa-pug-scss-ts/files/src/views/layout/_header.pug +++ b/packages/create-app/templates/mpa-pug-scss-ts/files/src/views/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/mpa-pug-scss-ts/files/webpack.config.js b/packages/create-app/templates/mpa-pug-scss-ts/files/webpack.config.js index 98c2ecc..71a90e0 100644 --- a/packages/create-app/templates/mpa-pug-scss-ts/files/webpack.config.js +++ b/packages/create-app/templates/mpa-pug-scss-ts/files/webpack.config.js @@ -1,37 +1,23 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'ts', - styles: 'scss', - appType: 'mpa', - templates: { - entry: 'src/views/pages/', - }, - resolve: { - alias: { - '@views': path.resolve(process.cwd(), 'src/views'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@scripts': path.resolve(process.cwd(), 'src/scripts'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'ts', + styles: 'scss', + appType: 'mpa', + templates: { + type: 'pug', + entry: 'src/views/pages/', + }, + resolve: { + alias: { + '@views': path.resolve(process.cwd(), 'src/views'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@scripts': path.resolve(process.cwd(), 'src/scripts'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/spa-pug-less-js/files/package.json b/packages/create-app/templates/spa-pug-less-js/files/package.json index a231578..90c1c67 100644 --- a/packages/create-app/templates/spa-pug-less-js/files/package.json +++ b/packages/create-app/templates/spa-pug-less-js/files/package.json @@ -16,12 +16,11 @@ "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-env": "^7.28.5", - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "babel-loader": "^10.0.0", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "less": "^4.5.1", @@ -33,12 +32,11 @@ "pug-plugin": "^6.1.0", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0", + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2", "core-js": "^3.48.0" } } diff --git a/packages/create-app/templates/spa-pug-less-js/files/src/assets/images/logo.svg b/packages/create-app/templates/spa-pug-less-js/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/spa-pug-less-js/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/spa-pug-less-js/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/spa-pug-less-js/files/src/shared/layout/_header.pug b/packages/create-app/templates/spa-pug-less-js/files/src/shared/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/spa-pug-less-js/files/src/shared/layout/_header.pug +++ b/packages/create-app/templates/spa-pug-less-js/files/src/shared/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/spa-pug-less-js/files/src/styles/main.less b/packages/create-app/templates/spa-pug-less-js/files/src/styles/main.less index 19481f4..79f5657 100644 --- a/packages/create-app/templates/spa-pug-less-js/files/src/styles/main.less +++ b/packages/create-app/templates/spa-pug-less-js/files/src/styles/main.less @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/spa-pug-less-js/files/webpack.config.js b/packages/create-app/templates/spa-pug-less-js/files/webpack.config.js index 03a46c2..bae7e70 100644 --- a/packages/create-app/templates/spa-pug-less-js/files/webpack.config.js +++ b/packages/create-app/templates/spa-pug-less-js/files/webpack.config.js @@ -1,38 +1,24 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'js', - styles: 'less', - appType: 'spa', - templates: { - entry: 'src/app/app.pug', - }, - resolve: { - alias: { - '@app': path.resolve(process.cwd(), 'src/app'), - '@pages': path.resolve(process.cwd(), 'src/pages'), - '@shared': path.resolve(process.cwd(), 'src/shared'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'js', + styles: 'less', + appType: 'spa', + templates: { + type: 'pug', + entry: 'src/app/app.pug', + }, + resolve: { + alias: { + '@app': path.resolve(process.cwd(), 'src/app'), + '@pages': path.resolve(process.cwd(), 'src/pages'), + '@shared': path.resolve(process.cwd(), 'src/shared'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/spa-pug-less-ts/files/package.json b/packages/create-app/templates/spa-pug-less-ts/files/package.json index 0664dd9..a514c2a 100644 --- a/packages/create-app/templates/spa-pug-less-ts/files/package.json +++ b/packages/create-app/templates/spa-pug-less-ts/files/package.json @@ -14,11 +14,10 @@ "test:build:cloudflare": "CF_PAGES=true npm run build" }, "devDependencies": { - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "less": "^4.5.1", @@ -32,11 +31,10 @@ "typescript": "^5.9.3", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0" + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2" } } diff --git a/packages/create-app/templates/spa-pug-less-ts/files/src/assets/images/logo.svg b/packages/create-app/templates/spa-pug-less-ts/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/spa-pug-less-ts/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/spa-pug-less-ts/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/spa-pug-less-ts/files/src/shared/layout/_header.pug b/packages/create-app/templates/spa-pug-less-ts/files/src/shared/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/spa-pug-less-ts/files/src/shared/layout/_header.pug +++ b/packages/create-app/templates/spa-pug-less-ts/files/src/shared/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/spa-pug-less-ts/files/src/styles/main.less b/packages/create-app/templates/spa-pug-less-ts/files/src/styles/main.less index 19481f4..79f5657 100644 --- a/packages/create-app/templates/spa-pug-less-ts/files/src/styles/main.less +++ b/packages/create-app/templates/spa-pug-less-ts/files/src/styles/main.less @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/spa-pug-less-ts/files/tsconfig.json b/packages/create-app/templates/spa-pug-less-ts/files/tsconfig.json index 7e5c568..83c480c 100644 --- a/packages/create-app/templates/spa-pug-less-ts/files/tsconfig.json +++ b/packages/create-app/templates/spa-pug-less-ts/files/tsconfig.json @@ -11,13 +11,15 @@ "@shared/*": ["shared/*"], "@styles/*": ["styles/*"], "@images/*": ["assets/images/*"], - "@icons/*": ["assets/icons*"] + "@icons/*": ["assets/icons/*"] }, "resolveJsonModule": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "isolatedModules": true, + "sourceMap": true }, "include": ["src/**/*"] } diff --git a/packages/create-app/templates/spa-pug-less-ts/files/webpack.config.js b/packages/create-app/templates/spa-pug-less-ts/files/webpack.config.js index d052d87..0ccc77b 100644 --- a/packages/create-app/templates/spa-pug-less-ts/files/webpack.config.js +++ b/packages/create-app/templates/spa-pug-less-ts/files/webpack.config.js @@ -1,38 +1,24 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'ts', - styles: 'less', - appType: 'spa', - templates: { - entry: 'src/app/app.pug', - }, - resolve: { - alias: { - '@app': path.resolve(process.cwd(), 'src/app'), - '@pages': path.resolve(process.cwd(), 'src/pages'), - '@shared': path.resolve(process.cwd(), 'src/shared'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'ts', + styles: 'less', + appType: 'spa', + templates: { + type: 'pug', + entry: 'src/app/app.pug', + }, + resolve: { + alias: { + '@app': path.resolve(process.cwd(), 'src/app'), + '@pages': path.resolve(process.cwd(), 'src/pages'), + '@shared': path.resolve(process.cwd(), 'src/shared'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/spa-pug-scss-js/files/package.json b/packages/create-app/templates/spa-pug-scss-js/files/package.json index d95a3f7..e48fc52 100644 --- a/packages/create-app/templates/spa-pug-scss-js/files/package.json +++ b/packages/create-app/templates/spa-pug-scss-js/files/package.json @@ -16,12 +16,11 @@ "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-env": "^7.28.5", - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "babel-loader": "^10.0.0", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "parse5": "^7.1.2", @@ -33,12 +32,11 @@ "sass-loader": "^14.1.1", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0", + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2", "core-js": "^3.48.0" } } diff --git a/packages/create-app/templates/spa-pug-scss-js/files/src/assets/images/logo.svg b/packages/create-app/templates/spa-pug-scss-js/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/spa-pug-scss-js/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/spa-pug-scss-js/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/spa-pug-scss-js/files/src/shared/layout/_header.pug b/packages/create-app/templates/spa-pug-scss-js/files/src/shared/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/spa-pug-scss-js/files/src/shared/layout/_header.pug +++ b/packages/create-app/templates/spa-pug-scss-js/files/src/shared/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/spa-pug-scss-js/files/src/styles/main.scss b/packages/create-app/templates/spa-pug-scss-js/files/src/styles/main.scss index 6200900..a2c6626 100644 --- a/packages/create-app/templates/spa-pug-scss-js/files/src/styles/main.scss +++ b/packages/create-app/templates/spa-pug-scss-js/files/src/styles/main.scss @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/spa-pug-scss-js/files/webpack.config.js b/packages/create-app/templates/spa-pug-scss-js/files/webpack.config.js index d017a6e..01239f6 100644 --- a/packages/create-app/templates/spa-pug-scss-js/files/webpack.config.js +++ b/packages/create-app/templates/spa-pug-scss-js/files/webpack.config.js @@ -1,38 +1,24 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'js', - styles: 'scss', - appType: 'spa', - templates: { - entry: 'src/app/app.pug' +module.exports = defineConfig({ + scripts: 'js', + styles: 'scss', + appType: 'spa', + templates: { + type: 'pug', + entry: 'src/app/app.pug', + }, + resolve: { + alias: { + '@app': path.resolve(process.cwd(), 'src/app'), + '@pages': path.resolve(process.cwd(), 'src/pages'), + '@shared': path.resolve(process.cwd(), 'src/shared'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - resolve: { - alias: { - '@app': path.resolve(process.cwd(), 'src/app'), - '@pages': path.resolve(process.cwd(), 'src/pages'), - '@shared': path.resolve(process.cwd(), 'src/shared'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins - } - } - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/templates/spa-pug-scss-ts/files/package.json b/packages/create-app/templates/spa-pug-scss-ts/files/package.json index eeb217b..a4d8e6b 100644 --- a/packages/create-app/templates/spa-pug-scss-ts/files/package.json +++ b/packages/create-app/templates/spa-pug-scss-ts/files/package.json @@ -14,11 +14,10 @@ "test:build:cloudflare": "CF_PAGES=true npm run build" }, "devDependencies": { - "@razerspine/build": "^1.0.0", + "@razerspine/build": "^1.0.2", "@types/node": "^25.1.0", "autoprefixer": "^10.4.23", "css-loader": "^6.10.0", - "dotenv": "^16.4.5", "html-webpack-plugin": "^5.6.6", "http-server": "^14.1.1", "parse5": "^7.1.2", @@ -32,11 +31,10 @@ "typescript": "^5.9.3", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.3", - "webpack-merge": "^5.10.0" + "webpack-dev-server": "^5.0.3" }, "dependencies": { - "@razerspine/runtime": "^1.0.1", - "@razerspine/ui": "^1.0.0" + "@razerspine/runtime": "^1.0.2", + "@razerspine/ui": "^1.0.2" } } diff --git a/packages/create-app/templates/spa-pug-scss-ts/files/src/assets/images/logo.svg b/packages/create-app/templates/spa-pug-scss-ts/files/src/assets/images/logo.svg index fe7cf78..88ab39d 100644 --- a/packages/create-app/templates/spa-pug-scss-ts/files/src/assets/images/logo.svg +++ b/packages/create-app/templates/spa-pug-scss-ts/files/src/assets/images/logo.svg @@ -1,5 +1,10 @@ - - - + + + + + + + + + diff --git a/packages/create-app/templates/spa-pug-scss-ts/files/src/shared/layout/_header.pug b/packages/create-app/templates/spa-pug-scss-ts/files/src/shared/layout/_header.pug index 1a42912..5f91373 100644 --- a/packages/create-app/templates/spa-pug-scss-ts/files/src/shared/layout/_header.pug +++ b/packages/create-app/templates/spa-pug-scss-ts/files/src/shared/layout/_header.pug @@ -4,7 +4,7 @@ header.header .header__row .header__col .logo - img.logo__icon(src='@images/logo.svg' alt='logo' loading='lazy') + img.logo__icon(src='@images/logo.svg' alt='logo' width='35' height='37' fetchpriority='high') .header__col a.btn.btn--icon.btn--icon-medium(href=gitHubLink target='_blank') svg.button-icon.icon--icon-medium(role="img") diff --git a/packages/create-app/templates/spa-pug-scss-ts/files/src/styles/main.scss b/packages/create-app/templates/spa-pug-scss-ts/files/src/styles/main.scss index 6200900..eabc828 100644 --- a/packages/create-app/templates/spa-pug-scss-ts/files/src/styles/main.scss +++ b/packages/create-app/templates/spa-pug-scss-ts/files/src/styles/main.scss @@ -10,11 +10,9 @@ } .layout-simple { - background-image: linear-gradient(to right, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - linear-gradient(to bottom, rgba(49, 46, 129, 0.2) 1px, transparent 1px), - radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), + background-image: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), radial-gradient(circle at 80% 70%, rgba(79, 70, 229, 0.1) 0%, transparent 50%); - background-size: 5rem 5rem, 5rem 5rem, 100% 100%, 100% 100%; + background-size: 100% 100%, 100% 100%; } .text-gradient-soft { @@ -38,6 +36,13 @@ .header { background: var(--bg-color); box-shadow: 0 2px 10px 0 rgba(99, 102, 241, 0.3); + + .logo { + &__icon { + width: 35px; + height: 37px; + } + } } .footer { diff --git a/packages/create-app/templates/spa-pug-scss-ts/files/tsconfig.json b/packages/create-app/templates/spa-pug-scss-ts/files/tsconfig.json index 7e5c568..83c480c 100644 --- a/packages/create-app/templates/spa-pug-scss-ts/files/tsconfig.json +++ b/packages/create-app/templates/spa-pug-scss-ts/files/tsconfig.json @@ -11,13 +11,15 @@ "@shared/*": ["shared/*"], "@styles/*": ["styles/*"], "@images/*": ["assets/images/*"], - "@icons/*": ["assets/icons*"] + "@icons/*": ["assets/icons/*"] }, "resolveJsonModule": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "isolatedModules": true, + "sourceMap": true }, "include": ["src/**/*"] } diff --git a/packages/create-app/templates/spa-pug-scss-ts/files/webpack.config.js b/packages/create-app/templates/spa-pug-scss-ts/files/webpack.config.js index 380bc01..f526398 100644 --- a/packages/create-app/templates/spa-pug-scss-ts/files/webpack.config.js +++ b/packages/create-app/templates/spa-pug-scss-ts/files/webpack.config.js @@ -1,38 +1,24 @@ const path = require('path'); -const { - createBaseConfig, - createDevConfig, - createProdConfig, -} = require('@razerspine/build'); +const {defineConfig} = require('@razerspine/build'); const uiKit = require('@razerspine/ui'); -module.exports = (env = {}, argv = {}) => { - const mode = argv?.mode || env?.mode || process.env.NODE_ENV || 'development'; - - const baseConfig = createBaseConfig({ - mode, - scripts: 'ts', - styles: 'scss', - appType: 'spa', - templates: { - entry: 'src/app/app.pug', - }, - resolve: { - alias: { - '@app': path.resolve(process.cwd(), 'src/app'), - '@pages': path.resolve(process.cwd(), 'src/pages'), - '@shared': path.resolve(process.cwd(), 'src/shared'), - '@styles': path.resolve(process.cwd(), 'src/styles'), - '@images': path.resolve(process.cwd(), 'src/assets/images'), - '@icons': path.resolve(process.cwd(), 'src/assets/icons'), - 'pug-mixins': uiKit.paths.mixins, - }, +module.exports = defineConfig({ + scripts: 'ts', + styles: 'scss', + appType: 'spa', + templates: { + type: 'pug', + entry: 'src/app/app.pug', + }, + resolve: { + alias: { + '@app': path.resolve(process.cwd(), 'src/app'), + '@pages': path.resolve(process.cwd(), 'src/pages'), + '@shared': path.resolve(process.cwd(), 'src/shared'), + '@styles': path.resolve(process.cwd(), 'src/styles'), + '@images': path.resolve(process.cwd(), 'src/assets/images'), + '@icons': path.resolve(process.cwd(), 'src/assets/icons'), + 'pug-mixins': uiKit.paths.mixins, }, - }); - - if (mode === 'development') { - return createDevConfig(baseConfig); - } - - return createProdConfig(baseConfig); -}; + }, +}); diff --git a/packages/create-app/tests/e2e/help.test.ts b/packages/create-app/tests/e2e/help.test.ts index b6da90d..5ab9984 100644 --- a/packages/create-app/tests/e2e/help.test.ts +++ b/packages/create-app/tests/e2e/help.test.ts @@ -2,15 +2,18 @@ import {runCLI} from '../helpers/run-cli'; import {describe, it} from 'vitest'; describe('Help Flag Output', () => { - it('should exit with code 0 for valid help flags', async () => { - await runCLI(['--help'], {expectedExitCode: 0}); - await runCLI(['-h'], {expectedExitCode: 0}); - }); - it('should exit with code 1 for invalid help commands', async () => { - await runCLI(['help'], {expectedExitCode: 1}); - await runCLI(['Help'], {expectedExitCode: 1}); - await runCLI(['H'], {expectedExitCode: 1}); - await runCLI(['h'], {expectedExitCode: 1}); - }); + it.each(['--help', '-h'])( + 'should exit with code 0 for valid help flag: %s', + async (flag) => { + await runCLI([flag], {expectedExitCode: 0}); + } + ); + + it.each(['help', 'Help', 'H', 'h'])( + 'should exit with code 1 for invalid help command: %s', + async (cmd) => { + await runCLI([cmd], {expectedExitCode: 1}); + } + ); }); diff --git a/packages/create-app/tests/e2e/version.test.ts b/packages/create-app/tests/e2e/version.test.ts index f4230c8..cec25d2 100644 --- a/packages/create-app/tests/e2e/version.test.ts +++ b/packages/create-app/tests/e2e/version.test.ts @@ -11,11 +11,10 @@ describe('CLI Version Flag', () => { await runCLI(['-v'], {expectedExitCode: 0}); }); - it('should fail for invalid version commands', async () => { - const invalidCommands = ['version', 'Version', 'V', 'v']; - - for (const cmd of invalidCommands) { + it.each(['version', 'Version', 'V', 'v'])( + 'should fail for invalid version command: %s', + async (cmd) => { await runCLI([cmd], {expectedExitCode: 1}); } - }); + ); }); diff --git a/packages/create-app/tests/integration/create-app.test.ts b/packages/create-app/tests/integration/create-app.test.ts index 26ca45f..97f5b91 100644 --- a/packages/create-app/tests/integration/create-app.test.ts +++ b/packages/create-app/tests/integration/create-app.test.ts @@ -3,6 +3,7 @@ import path from 'path'; import {createApp} from '../../src/core/create-app'; import {log} from '../../src/utils'; import type * as StepsNamespace from '../../src/steps'; +import type {LoadedTemplate} from '../../src/templates/types'; vi.mock('ora', () => ({ default: vi.fn(() => ({ @@ -28,13 +29,10 @@ vi.mock('../../src/steps', (): typeof StepsNamespace => { const createStepMock = () => vi.fn(() => vi.fn(async (ctx) => ctx)); return { - resolveTemplateStep: vi.fn(async (ctx) => ({ - ...ctx, - template: {filesPath: '/mock/path'} as any - })), prepareDirectoryStep: createStepMock() as any, copyTemplateStep: createStepMock() as any, patchPackageStep: createStepMock() as any, + writeGitignoreStep: createStepMock() as any, installDepsStep: createStepMock() as any, }; }); @@ -52,13 +50,20 @@ describe('createApp (Unit)', () => { process.cwd = originalCwd; }); - it('should build correct BasePipelineContext and execute pipeline successfully', async () => { - const {resolveTemplateStep} = await import('../../src/steps'); + it('should build correct TemplateResolvedContext and execute pipeline successfully', async () => { + const {prepareDirectoryStep} = await import('../../src/steps'); + + const mockTemplate: LoadedTemplate = { + key: 'spa-scss-ts', + meta: {name: 'spa-scss-ts', description: 'SPA + SCSS + TypeScript'}, + path: '/mock/templates/spa-scss-ts', + filesPath: '/mock/templates/spa-scss-ts/files', + }; const options = { projectName: 'my-test-app', - templateKey: 'spa-scss-ts' as any, - appType: 'spa' as any, + template: mockTemplate, + appType: 'spa' as const, noInstall: true, dryRun: false, pm: 'pnpm' as const @@ -66,10 +71,13 @@ describe('createApp (Unit)', () => { await createApp(options); - expect(resolveTemplateStep).toHaveBeenCalledWith( + const stepFn = vi.mocked(prepareDirectoryStep).mock.results[0].value; + + expect(stepFn).toHaveBeenCalledWith( expect.objectContaining({ projectName: 'my-test-app', templateKey: 'spa-scss-ts', + template: mockTemplate, appType: 'spa', targetDir: path.resolve('/mock/cwd', 'my-test-app'), noInstall: true, diff --git a/packages/create-app/tests/unit/steps/resolve-template-step.test.ts b/packages/create-app/tests/unit/steps/resolve-template-step.test.ts deleted file mode 100644 index 45fab8b..0000000 --- a/packages/create-app/tests/unit/steps/resolve-template-step.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {describe, it, expect, vi} from 'vitest'; -import {resolveTemplateStep} from '../../../src/steps'; -import {createTemplateService} from '../../../src/core/template.service'; - -vi.mock('../../../src/core/template.service', () => ({ - createTemplateService: vi.fn() -})); - -describe('resolveTemplateStep', () => { - it('should resolve template and enrich context', async () => { - const mockTemplate = { - name: 'test-template', - filesPath: '/path' - }; - const mockService = { - getByKey: vi.fn().mockReturnValue(mockTemplate) - }; - vi.mocked(createTemplateService).mockReturnValue(mockService as any); - - const ctx = {templateKey: 'web-app'} as any; - const result = await resolveTemplateStep(ctx); - - expect(mockService.getByKey).toHaveBeenCalledWith('web-app'); - expect(result.template).toBe(mockTemplate); - expect(result.templateKey).toBe('web-app'); - }); -}); diff --git a/packages/create-app/tests/unit/steps/write-gitignore-step.test.ts b/packages/create-app/tests/unit/steps/write-gitignore-step.test.ts new file mode 100644 index 0000000..7aae3b5 --- /dev/null +++ b/packages/create-app/tests/unit/steps/write-gitignore-step.test.ts @@ -0,0 +1,44 @@ +import {describe, it, expect, vi, beforeEach} from 'vitest'; +import {writeGitignoreStep} from '../../../src/steps'; +import {writeGitignore} from '../../../src/utils'; + +vi.mock('../../../src/utils', () => ({ + writeGitignore: vi.fn() +})); + +describe('writeGitignoreStep', () => { + const mockSpinner = { + start: vi.fn(), + succeed: vi.fn(), + fail: vi.fn(), + info: vi.fn() + } as any; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call writeGitignore with targetDir', async () => { + const ctx = {targetDir: '/path/my-app', dryRun: false} as any; + await writeGitignoreStep(mockSpinner)(ctx); + + expect(writeGitignore).toHaveBeenCalledWith('/path/my-app'); + expect(mockSpinner.succeed).toHaveBeenCalled(); + }); + + it('should skip and log info in dry-run mode', async () => { + const ctx = {targetDir: '/path/my-app', dryRun: true} as any; + await writeGitignoreStep(mockSpinner)(ctx); + + expect(writeGitignore).not.toHaveBeenCalled(); + expect(mockSpinner.info).toHaveBeenCalledWith('[dry-run] .gitignore would be written'); + }); + + it('should call spinner.fail if writing fails', async () => { + vi.mocked(writeGitignore).mockRejectedValue(new Error('EACCES')); + const ctx = {dryRun: false} as any; + + await expect(writeGitignoreStep(mockSpinner)(ctx)).rejects.toThrow(); + expect(mockSpinner.fail).toHaveBeenCalledWith('Failed to write .gitignore'); + }); +}); diff --git a/packages/create-app/tests/unit/utils/patch-package.test.ts b/packages/create-app/tests/unit/utils/patch-package.test.ts index 04bcb61..2bafd1f 100644 --- a/packages/create-app/tests/unit/utils/patch-package.test.ts +++ b/packages/create-app/tests/unit/utils/patch-package.test.ts @@ -1,10 +1,20 @@ -import {describe, it, expect, beforeEach, afterEach} from 'vitest'; +import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest'; import fs from 'node:fs/promises'; import path from 'node:path'; import {patchPackageJson} from '../../../src/utils'; import {createTempDir} from '../../helpers/temp-dir'; import {cleanupDirectory} from '../../helpers/cleanup-directory'; +vi.mock('node:child_process', () => ({ + execSync: vi.fn((cmd: string) => { + if (cmd.includes('yarn')) return Buffer.from('1.22.19\n'); + if (cmd.includes('pnpm')) return Buffer.from('8.15.5\n'); + if (cmd.includes('bun')) return Buffer.from('1.1.0\n'); + if (cmd.includes('npm')) return Buffer.from('10.5.0\n'); + throw new Error('Command not found'); + }) +})); + describe('patchPackageJson (Unit)', () => { let tempDir: string; let pkgPath: string; @@ -28,6 +38,7 @@ describe('patchPackageJson (Unit)', () => { afterEach(() => { cleanupDirectory(tempDir); + vi.clearAllMocks(); }); it('should set project name, private flag, and packageManager field', async () => { @@ -37,7 +48,8 @@ describe('patchPackageJson (Unit)', () => { expect(updatedPkg.name).toBe('my-awesome-app'); expect(updatedPkg.private).toBe(true); - expect(updatedPkg.packageManager).toBe('yarn@latest'); + + expect(updatedPkg.packageManager).toBe('yarn@1.22.19'); }); it('should replace "npm run" prefixes with the correct package manager command', async () => { @@ -65,6 +77,5 @@ describe('patchPackageJson (Unit)', () => { expect(updatedPkg.scripts.build).toContain('pnpm clean'); expect(updatedPkg.scripts.build).toContain('pnpm compile'); - expect(updatedPkg.scripts.build).not.toContain('npm run'); }); }); diff --git a/packages/create-app/tests/unit/utils/write-gitignore.test.ts b/packages/create-app/tests/unit/utils/write-gitignore.test.ts new file mode 100644 index 0000000..2e3cdd3 --- /dev/null +++ b/packages/create-app/tests/unit/utils/write-gitignore.test.ts @@ -0,0 +1,45 @@ +import {describe, it, expect, beforeEach, afterEach} from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import {writeGitignore} from '../../../src/utils'; +import {createTempDir} from '../../helpers/temp-dir'; +import {cleanupDirectory} from '../../helpers/cleanup-directory'; + +describe('writeGitignore (Unit)', () => { + let targetDir: string; + + beforeEach(() => { + targetDir = createTempDir(); + }); + + afterEach(() => { + cleanupDirectory(targetDir); + }); + + it('should create a .gitignore file in the target directory', async () => { + await writeGitignore(targetDir); + + expect(fs.existsSync(path.join(targetDir, '.gitignore'))).toBe(true); + }); + + it('should include node_modules in .gitignore', async () => { + await writeGitignore(targetDir); + + const content = fs.readFileSync(path.join(targetDir, '.gitignore'), 'utf-8'); + expect(content).toContain('node_modules/'); + }); + + it('should include dist in .gitignore', async () => { + await writeGitignore(targetDir); + + const content = fs.readFileSync(path.join(targetDir, '.gitignore'), 'utf-8'); + expect(content).toContain('dist/'); + }); + + it('should include .env in .gitignore', async () => { + await writeGitignore(targetDir); + + const content = fs.readFileSync(path.join(targetDir, '.gitignore'), 'utf-8'); + expect(content).toContain('.env'); + }); +}); diff --git a/packages/ui/tests/fixtures/with-runtime/package.json b/packages/ui/tests/fixtures/with-runtime/package.json index 5e3885e..41ab3c8 100644 --- a/packages/ui/tests/fixtures/with-runtime/package.json +++ b/packages/ui/tests/fixtures/with-runtime/package.json @@ -5,7 +5,7 @@ "pug": "^3.0.4" }, "devDependencies": { - "@razerspine/runtime": "^1.0.1", + "@razerspine/runtime": "^1.0.2", "@types/pug": "^2.0.10" } }