From 7e05a5608db2b4bd2ff1dd18c87774bf20ba171b Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Sat, 5 Jul 2025 19:30:14 -0400 Subject: [PATCH 01/12] Add release checklist for v1.1.0 planning --- RELEASE_CHECKLIST_v1.1.0.md | 231 ++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 RELEASE_CHECKLIST_v1.1.0.md diff --git a/RELEASE_CHECKLIST_v1.1.0.md b/RELEASE_CHECKLIST_v1.1.0.md new file mode 100644 index 0000000..ea269ff --- /dev/null +++ b/RELEASE_CHECKLIST_v1.1.0.md @@ -0,0 +1,231 @@ +# Release Checklist v1.1.0 + +## Version Comparison + +### Current State (v1.0.1) āœ… **RELEASED** +- [x] Fixed CLI executable issue (critical bug fix) +- [x] Added `--verbose` and `--dry-run` flags +- [x] Enhanced error handling and validation +- [x] Improved type inference for arrays and objects +- [x] Added chalk colors for better UX +- [x] Comprehensive documentation (README, CONTRIBUTING, etc.) +- [x] Professional automation (GitHub Actions, Dependabot) +- [x] Published to npm as v1.0.1 + +### Planned v1.1.0 Features (Future) + +#### šŸŽÆ Major Features +- [ ] **Nested Object Interface Generation** + - Generate proper TypeScript interfaces for nested objects + - Support for complex object structures + - Better type inference for nested properties + +- [ ] **Testing Infrastructure** + - Unit tests for core functionality + - Integration tests for CLI commands + - Test coverage reporting + - CI/CD test automation + +- [ ] **Configuration File Support** + - `.react-state-cli.json` configuration file + - Customizable output directories + - Template customization options + - Naming convention preferences + +#### šŸ”§ Enhanced Features +- [ ] **Advanced Type Inference** + - Better handling of arrays with specific types + - Union types for mixed arrays + - Optional property detection + - Enum generation for string literals + +- [ ] **Template System** + - User-defined custom templates + - Template inheritance + - Plugin system for extensions + - Multiple template formats + +- [ ] **Multi-Slice Generation** + - Batch processing of multiple initState files + - Workspace-wide Redux setup + - Automatic store configuration + - Index file generation + +#### šŸ› ļø Developer Experience +- [ ] **VS Code Extension** + - Syntax highlighting for templates + - IntelliSense for generated code + - Right-click context menu + - Live preview of generated code + +- [ ] **Enhanced CLI** + - Interactive mode for configuration + - Progress bars for long operations + - Better error messages with suggestions + - Help system with examples + +#### šŸ“Š Quality & Performance +- [ ] **Performance Optimizations** + - Faster file processing + - Memory usage optimization + - Parallel processing for multiple files + - Caching for repeated operations + +- [ ] **Code Quality** + - ESLint rules for generated code + - Prettier formatting options + - Code quality metrics + - Documentation generation + +## Development Timeline + +### Phase 1: Foundation (Week 1-2) +- [ ] Set up comprehensive testing infrastructure +- [ ] Implement nested object interface generation +- [ ] Add configuration file support +- [ ] Enhance type inference capabilities + +### Phase 2: Advanced Features (Week 3-4) +- [ ] Custom template system +- [ ] Multi-slice generation +- [ ] VS Code extension development +- [ ] Performance optimizations + +### Phase 3: Polish & Release (Week 5-6) +- [ ] Documentation updates +- [ ] Migration guide from v1.0.1 +- [ ] Beta testing with community +- [ ] Final release preparations + +## Technical Requirements + +### Dependencies to Add +- [ ] `jest` - Testing framework +- [ ] `@types/jest` - TypeScript support for Jest +- [ ] `ts-jest` - TypeScript preprocessor for Jest +- [ ] `inquirer` - Interactive CLI prompts +- [ ] `glob` - File pattern matching +- [ ] `yaml` - YAML configuration support + +### Breaking Changes (if any) +- [ ] None planned - maintain backward compatibility +- [ ] Configuration file is optional +- [ ] New features are additive + +### Migration Guide +- [ ] v1.0.1 → v1.1.0 should be seamless +- [ ] New features are opt-in +- [ ] Existing generated code remains valid +- [ ] Configuration file provides new capabilities + +## Testing Strategy + +### Unit Tests +- [ ] Core generation logic +- [ ] Type inference functions +- [ ] Template processing +- [ ] Configuration parsing +- [ ] Error handling + +### Integration Tests +- [ ] CLI command execution +- [ ] File system operations +- [ ] Template rendering +- [ ] Multi-file generation +- [ ] Configuration file loading + +### End-to-End Tests +- [ ] Complete workflow testing +- [ ] Real project integration +- [ ] Performance benchmarks +- [ ] Cross-platform compatibility + +## Release Criteria + +### Must Have +- [ ] All planned features implemented +- [ ] Comprehensive test coverage (>90%) +- [ ] Documentation updated +- [ ] No breaking changes +- [ ] Performance regression testing + +### Nice to Have +- [ ] VS Code extension beta +- [ ] Community feedback integration +- [ ] Advanced template examples +- [ ] Video tutorials + +## Post-Release Tasks + +### Immediate (Week 1) +- [ ] Monitor npm download metrics +- [ ] Address any critical issues +- [ ] Community support and feedback +- [ ] Documentation improvements + +### Short-term (Month 1) +- [ ] VS Code extension public release +- [ ] Community template sharing +- [ ] Performance monitoring +- [ ] Feature usage analytics + +### Long-term (Month 2-3) +- [ ] Plan v1.2.0 features +- [ ] Expand ecosystem integrations +- [ ] Conference talks and presentations +- [ ] Partnership opportunities + +## Success Metrics + +### Downloads & Usage +- [ ] Target: 50% increase in weekly downloads +- [ ] Target: 100+ GitHub stars +- [ ] Target: 10+ community contributions +- [ ] Target: 5+ community templates + +### Quality Metrics +- [ ] Target: <5 critical issues reported +- [ ] Target: <24hr average issue response time +- [ ] Target: 95%+ test coverage maintained +- [ ] Target: <2sec average generation time + +### Community Growth +- [ ] Target: 20+ active contributors +- [ ] Target: 500+ Discord/community members +- [ ] Target: 10+ external blog posts/reviews +- [ ] Target: 5+ integration examples + +--- + +## Notes + +### v1.0.1 Success Summary +- āœ… Successfully published to npm +- āœ… All automation working (GitHub Actions, Dependabot) +- āœ… Professional documentation complete +- āœ… Community ready for contributions +- āœ… Solid foundation for future growth + +### v1.1.0 Focus Areas +- šŸŽÆ **Quality First**: Comprehensive testing before new features +- šŸ”§ **Developer Experience**: Make it easier and more powerful to use +- šŸ“Š **Performance**: Ensure it scales well with larger projects +- šŸ¤ **Community**: Build ecosystem around the tool + +### Development Approach +- **Incremental Development**: Small, testable changes +- **Community Feedback**: Regular beta releases for testing +- **Backward Compatibility**: Never break existing workflows +- **Documentation First**: Document features before implementation + +### Risk Mitigation +- **Feature Creep**: Stick to planned scope +- **Breaking Changes**: Avoid at all costs +- **Performance**: Regular benchmarking +- **Community**: Maintain active communication + +--- + +**Estimated Development Time: 4-6 weeks** +**Target Release Date: Q2 2025** +**Confidence Level: High** (based on v1.0.1 success) \ No newline at end of file From b6408362145e6a6c355104bdf73013dbf078ebcd Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Sun, 6 Jul 2025 15:11:10 -0400 Subject: [PATCH 02/12] fix: update workflow branches from main to master --- .github/workflows/ci.yml | 4 ++-- .github/workflows/tag-and-release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 562b5d3..aedebc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main, develop ] + branches: [ master, develop, release/* ] pull_request: - branches: [ main, develop ] + branches: [ master, develop ] jobs: test: diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml index 8398914..d297f5e 100644 --- a/.github/workflows/tag-and-release.yml +++ b/.github/workflows/tag-and-release.yml @@ -2,7 +2,7 @@ name: Tag and Release on: push: - branches: [ main ] + branches: [ master ] paths: [ 'package.json' ] jobs: From 815619ae3ea76c2299046b867e86ce0402ad6e2a Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Sun, 6 Jul 2025 15:16:22 -0400 Subject: [PATCH 03/12] fix: resolve ESLint configuration and remove dependabot - Fix ESLint configuration by removing problematic @typescript-eslint/recommended - Update dependencies to compatible versions - Remove dependabot configuration to reduce complexity - Fix unused variable in utils.ts - Apply code formatting fixes with prettier - All tests and linting now pass successfully --- .eslintrc.json | 3 +- .github/dependabot.yml | 39 ----- package-lock.json | 349 ++++++++++++++++++++++++----------------- package.json | 6 +- src/generate.ts | 178 +++++++++++---------- src/index.ts | 17 +- src/utils.ts | 78 +++++++-- 7 files changed, 379 insertions(+), 291 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.eslintrc.json b/.eslintrc.json index ff73f97..2e38e93 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,8 +4,7 @@ "es2022": true }, "extends": [ - "eslint:recommended", - "@typescript-eslint/recommended" + "eslint:recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 02cbd88..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,39 +0,0 @@ -version: 2 -updates: - # Enable version updates for npm - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - time: "09:00" - open-pull-requests-limit: 10 - reviewers: - - "Utsav-Virani" - assignees: - - "Utsav-Virani" - commit-message: - prefix: "deps" - include: "scope" - labels: - - "dependencies" - - "automated" - - # Enable version updates for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - time: "09:00" - open-pull-requests-limit: 5 - reviewers: - - "Utsav-Virani" - assignees: - - "Utsav-Virani" - commit-message: - prefix: "ci" - include: "scope" - labels: - - "github-actions" - - "automated" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3f7a410..5c2d8c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,9 +24,9 @@ "devDependencies": { "@types/ejs": "^3.1.5", "@types/fs-extra": "^11.0.4", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.0.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "eslint": "^8.57.0", "prettier": "^3.0.0" } }, @@ -95,6 +95,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", @@ -121,6 +145,30 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -330,13 +378,6 @@ "@types/node": "*" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsonfile": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", @@ -356,42 +397,33 @@ "undici-types": "~7.8.0" } }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -400,27 +432,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -429,17 +461,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -447,26 +479,26 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -475,13 +507,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -489,23 +521,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -517,70 +549,41 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -706,13 +709,12 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -984,6 +986,17 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1001,17 +1014,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10.13.0" + "node": "*" } }, "node_modules/espree": { @@ -1101,6 +1114,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1146,15 +1171,6 @@ "minimatch": "^5.0.1" } }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -1262,15 +1278,40 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/globals": { @@ -1463,6 +1504,16 @@ "node": ">=10" } }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jake/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1479,6 +1530,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1601,15 +1664,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { diff --git a/package.json b/package.json index 71fbbf6..c00bf1e 100644 --- a/package.json +++ b/package.json @@ -54,9 +54,9 @@ "devDependencies": { "@types/ejs": "^3.1.5", "@types/fs-extra": "^11.0.4", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.0.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "eslint": "^8.57.0", "prettier": "^3.0.0" }, "bin": { diff --git a/src/generate.ts b/src/generate.ts index d44a2fb..1cce130 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -1,181 +1,195 @@ -import path from "path"; -import fs from "fs-extra"; -import chalk from "chalk"; -import { parseInitState } from "./utils"; -import ejs from "ejs"; +import path from 'path' +import fs from 'fs-extra' +import chalk from 'chalk' +import { parseInitState } from './utils' +import ejs from 'ejs' export interface GenerateOptions { - verbose?: boolean; - dryRun?: boolean; + verbose?: boolean + dryRun?: boolean } export async function generateFilesFromInitState( - initStatePath: string, + initStatePath: string, options: GenerateOptions = {} ): Promise { try { - const sliceDir = path.dirname(initStatePath); - const sliceName = path.basename(sliceDir); - const reduxDir = path.resolve(sliceDir, ".."); // goes to /redux - const stateFilePath = path.resolve(reduxDir, "state.ts"); + const sliceDir = path.dirname(initStatePath) + const sliceName = path.basename(sliceDir) + const reduxDir = path.resolve(sliceDir, '..') // goes to /redux + const stateFilePath = path.resolve(reduxDir, 'state.ts') if (options.verbose) { - console.log(chalk.blue(`šŸ“‚ Slice directory: ${sliceDir}`)); - console.log(chalk.blue(`šŸ“ Slice name: ${sliceName}`)); + console.log(chalk.blue(`šŸ“‚ Slice directory: ${sliceDir}`)) + console.log(chalk.blue(`šŸ“ Slice name: ${sliceName}`)) } - const initState = await parseInitState(initStatePath); - const keys = Object.keys(initState); + const initState = await parseInitState(initStatePath) + const keys = Object.keys(initState) if (keys.length === 0) { - throw new Error("Initial state object is empty"); + throw new Error('Initial state object is empty') } // --- Generate slice.ts --- const actionCases = keys .map( - (key) => + key => ` set${capitalize( key )}: (state, action: PayloadAction<${inferType(initState[key])}>) => { state.${key} = action.payload },` ) - .join("\n"); + .join('\n') - const actionExports = keys.map((key) => `set${capitalize(key)}`).join(", "); + const actionExports = keys.map(key => `set${capitalize(key)}`).join(', ') const sliceTemplate = await fs.readFile( - path.resolve(__dirname, "../templates/slice.ts.tpl"), - "utf-8" - ); - const interfaceName = `${capitalize(sliceName)}State`; + path.resolve(__dirname, '../templates/slice.ts.tpl'), + 'utf-8' + ) + const interfaceName = `${capitalize(sliceName)}State` const renderedSlice = ejs.render(sliceTemplate, { sliceName, initialStateString: JSON.stringify(initState, null, 2), actionCases, actionExports, - interfaceName, - }); + interfaceName + }) if (options.dryRun) { - console.log(chalk.yellow("šŸ” [DRY RUN] Would generate slice.ts:")); - console.log(chalk.gray(renderedSlice)); + console.log(chalk.yellow('šŸ” [DRY RUN] Would generate slice.ts:')) + console.log(chalk.gray(renderedSlice)) } else { - await fs.writeFile(path.resolve(sliceDir, "slice.ts"), renderedSlice); - console.log(chalk.green(`āœ… slice.ts generated at ${sliceDir}`)); + await fs.writeFile(path.resolve(sliceDir, 'slice.ts'), renderedSlice) + console.log(chalk.green(`āœ… slice.ts generated at ${sliceDir}`)) } // --- Generate reducers.ts --- const reducersTemplate = await fs.readFile( - path.resolve(__dirname, "../templates/reducers.ts.tpl"), - "utf-8" - ); + path.resolve(__dirname, '../templates/reducers.ts.tpl'), + 'utf-8' + ) if (options.dryRun) { - console.log(chalk.yellow("šŸ” [DRY RUN] Would generate reducers.ts")); + console.log(chalk.yellow('šŸ” [DRY RUN] Would generate reducers.ts')) } else { - await fs.writeFile(path.resolve(sliceDir, "reducers.ts"), reducersTemplate); - console.log(chalk.green(`āœ… reducers.ts generated at ${sliceDir}`)); + await fs.writeFile( + path.resolve(sliceDir, 'reducers.ts'), + reducersTemplate + ) + console.log(chalk.green(`āœ… reducers.ts generated at ${sliceDir}`)) } // --- Generate types.ts --- const interfaceLines = Object.entries(initState).map(([key, value]) => { - const type = inferType(value); - return ` /** ${getTypeDescription(value)} */\n ${key}: ${type}`; - }); + const type = inferType(value) + return ` /** ${getTypeDescription(value)} */\n ${key}: ${type}` + }) const interfaceString = `export interface ${interfaceName} {\n${interfaceLines.join( - "\n" - )}\n}`; + '\n' + )}\n}` + + const typesPath = path.resolve(sliceDir, 'types.ts') - const typesPath = path.resolve(sliceDir, "types.ts"); - if (options.dryRun) { - console.log(chalk.yellow("šŸ” [DRY RUN] Would generate types.ts:")); - console.log(chalk.gray(interfaceString)); + console.log(chalk.yellow('šŸ” [DRY RUN] Would generate types.ts:')) + console.log(chalk.gray(interfaceString)) } else { - await fs.writeFile(typesPath, interfaceString); - console.log(chalk.green(`āœ… types.ts generated at ${typesPath}`)); + await fs.writeFile(typesPath, interfaceString) + console.log(chalk.green(`āœ… types.ts generated at ${typesPath}`)) } // --- Generate or update state.ts --- const stateTemplatePath = path.resolve( __dirname, - "../templates/state.ts.tpl" - ); - const stateTemplate = await fs.readFile(stateTemplatePath, "utf-8"); + '../templates/state.ts.tpl' + ) + const stateTemplate = await fs.readFile(stateTemplatePath, 'utf-8') const renderedState = ejs.render(stateTemplate, { sliceName, interfaceName - }); + }) - const stateExists = await fs.pathExists(stateFilePath); + const stateExists = await fs.pathExists(stateFilePath) if (!stateExists) { if (options.dryRun) { - console.log(chalk.yellow("šŸ” [DRY RUN] Would create state.ts:")); - console.log(chalk.gray(renderedState)); + console.log(chalk.yellow('šŸ” [DRY RUN] Would create state.ts:')) + console.log(chalk.gray(renderedState)) } else { - await fs.writeFile(stateFilePath, renderedState); - console.log(chalk.green(`āœ… state.ts created at ${stateFilePath}`)); + await fs.writeFile(stateFilePath, renderedState) + console.log(chalk.green(`āœ… state.ts created at ${stateFilePath}`)) } } else { if (options.verbose) { - console.log(chalk.yellow(`āš ļø state.ts already exists at ${stateFilePath}`)); - console.log(chalk.blue(`šŸ’” Consider manually adding: ${sliceName}: ${interfaceName}`)); + console.log( + chalk.yellow(`āš ļø state.ts already exists at ${stateFilePath}`) + ) + console.log( + chalk.blue( + `šŸ’” Consider manually adding: ${sliceName}: ${interfaceName}` + ) + ) } } // Summary if (options.verbose && !options.dryRun) { - console.log(chalk.cyan(`\nšŸ“Š Generated files summary:`)); - console.log(chalk.cyan(` • slice.ts - Redux slice with ${keys.length} actions`)); - console.log(chalk.cyan(` • reducers.ts - Reducer export`)); - console.log(chalk.cyan(` • types.ts - TypeScript interface`)); - console.log(chalk.cyan(` • state.ts - ${stateExists ? 'already exists' : 'created'}`)); + console.log(chalk.cyan('\nšŸ“Š Generated files summary:')) + console.log( + chalk.cyan(` • slice.ts - Redux slice with ${keys.length} actions`) + ) + console.log(chalk.cyan(' • reducers.ts - Reducer export')) + console.log(chalk.cyan(' • types.ts - TypeScript interface')) + console.log( + chalk.cyan( + ` • state.ts - ${stateExists ? 'already exists' : 'created'}` + ) + ) } - } catch (error) { if (error instanceof Error) { - throw new Error(`Failed to generate files: ${error.message}`); + throw new Error(`Failed to generate files: ${error.message}`) } - throw error; + throw error } } function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); + return str.charAt(0).toUpperCase() + str.slice(1) } function inferType(value: any): string { - if (value === null) return "null"; - if (value === undefined) return "undefined"; + if (value === null) return 'null' + if (value === undefined) return 'undefined' if (Array.isArray(value)) { - if (value.length === 0) return "any[]"; + if (value.length === 0) return 'any[]' // Try to infer array element type from first element - const firstElementType = inferType(value[0]); - return `${firstElementType}[]`; + const firstElementType = inferType(value[0]) + return `${firstElementType}[]` } - if (typeof value === "object") { + if (typeof value === 'object') { // For objects, we could either use a generic Record type or create nested interfaces // For now, let's use a more specific type - if (Object.keys(value).length === 0) return "Record"; - return "Record"; // Could be enhanced to generate nested interfaces + if (Object.keys(value).length === 0) return 'Record' + return 'Record' // Could be enhanced to generate nested interfaces } - return typeof value; + return typeof value } function getTypeDescription(value: any): string { - if (value === null) return "Nullable value"; - if (value === undefined) return "Undefined value"; + if (value === null) return 'Nullable value' + if (value === undefined) return 'Undefined value' if (Array.isArray(value)) { - return `Array with ${value.length} initial items`; + return `Array with ${value.length} initial items` } - if (typeof value === "object") { - return `Object with ${Object.keys(value).length} properties`; + if (typeof value === 'object') { + return `Object with ${Object.keys(value).length} properties` } - return `${typeof value} value`; + return `${typeof value} value` } diff --git a/src/index.ts b/src/index.ts index 93601b3..a9819d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,16 +21,16 @@ program .action(async (initStatePath, options) => { try { const fullPath = path.resolve(process.cwd(), initStatePath) - + // Validate input file exists - if (!await fs.pathExists(fullPath)) { + if (!(await fs.pathExists(fullPath))) { console.error(chalk.red(`āŒ Error: File not found: ${fullPath}`)) process.exit(1) } // Validate it's a TypeScript file if (!fullPath.endsWith('.ts') && !fullPath.endsWith('.js')) { - console.error(chalk.red(`āŒ Error: File must be a .ts or .js file`)) + console.error(chalk.red('āŒ Error: File must be a .ts or .js file')) process.exit(1) } @@ -39,11 +39,14 @@ program } await generateFilesFromInitState(fullPath, options) - - console.log(chalk.green(`šŸŽ‰ Redux boilerplate generated successfully!`)) - + + console.log(chalk.green('šŸŽ‰ Redux boilerplate generated successfully!')) } catch (error) { - console.error(chalk.red(`āŒ Error: ${error instanceof Error ? error.message : 'Unknown error'}`)) + console.error( + chalk.red( + `āŒ Error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + ) if (options.verbose && error instanceof Error) { console.error(chalk.red(error.stack)) } diff --git a/src/utils.ts b/src/utils.ts index 37bfeb0..3627027 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,10 +7,12 @@ import fs from 'fs-extra' * @returns Promise> - The parsed initial state object * @throws Error if file is invalid or doesn't export proper object */ -export async function parseInitState(filePath: string): Promise> { +export async function parseInitState( + filePath: string +): Promise> { try { // Verify file exists - if (!await fs.pathExists(filePath)) { + if (!(await fs.pathExists(filePath))) { throw new Error(`File not found: ${filePath}`) } @@ -22,15 +24,19 @@ export async function parseInitState(filePath: string): Promise - } catch (error) { if (error instanceof Error) { // Re-throw with more context - throw new Error(`Failed to parse initial state from ${path.basename(filePath)}: ${error.message}`) + throw new Error( + `Failed to parse initial state from ${path.basename(filePath)}: ${error.message}` + ) } throw error } @@ -72,12 +81,48 @@ export async function parseInitState(filePath: string): Promise Date: Sun, 6 Jul 2025 15:21:24 -0400 Subject: [PATCH 04/12] feat: convert to ES modules and improve code formatting - Change package.json type from commonjs to module - Improve code formatting in src/utils.ts with better line breaks - Rebuild dist directory with latest changes - Maintain compatibility with existing workflows --- package.json | 2 +- tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c00bf1e..d3c07b6 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "bugs": { "url": "https://github.com/Utsav-Virani/react-state-cli/issues" }, - "type": "commonjs", + "type": "module", "dependencies": { "@reduxjs/toolkit": "^2.8.2", "chalk": "^5.4.1", diff --git a/tsconfig.json b/tsconfig.json index ae698dd..2040472 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es2020", "lib": ["es2020"], - "module": "commonjs", + "module": "ESNext", "outDir": "./dist", "rootDir": "./src", "strict": true, From bd6155e46f4611d3627438a8677f01dd38c58f19 Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Sun, 6 Jul 2025 15:35:45 -0400 Subject: [PATCH 05/12] feat: complete ES modules support with Node.js 18 compatibility - Fix ES module imports by adding .js extensions - Implement TypeScript file handling with ts-node for ES modules - Replace __dirname with import.meta.url for ES module compatibility - Add comprehensive TypeScript import fallback mechanism - Remove unused imports and fix linting issues - Apply code formatting with prettier - All tests passing: dry-run and manual modes work correctly - Full Node.js 18+ compatibility maintained --- package-lock.json | 1 + package.json | 3 ++- src/generate.ts | 6 ++++- src/index.ts | 2 +- src/utils.ts | 56 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c2d8c7..55e0fb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "devDependencies": { "@types/ejs": "^3.1.5", "@types/fs-extra": "^11.0.4", + "@types/node": "^24.0.10", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "eslint": "^8.57.0", diff --git a/package.json b/package.json index d3c07b6..d3e72e8 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "devDependencies": { "@types/ejs": "^3.1.5", "@types/fs-extra": "^11.0.4", + "@types/node": "^24.0.10", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "eslint": "^8.57.0", @@ -62,4 +63,4 @@ "bin": { "react-state-cli": "./dist/index.js" } -} \ No newline at end of file +} diff --git a/src/generate.ts b/src/generate.ts index 1cce130..391e055 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -1,8 +1,12 @@ import path from 'path' import fs from 'fs-extra' import chalk from 'chalk' -import { parseInitState } from './utils' +import { parseInitState } from './utils.js' import ejs from 'ejs' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) export interface GenerateOptions { verbose?: boolean diff --git a/src/index.ts b/src/index.ts index a9819d2..50d40a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { Command } from 'commander' import path from 'path' import fs from 'fs-extra' import chalk from 'chalk' -import { generateFilesFromInitState } from './generate' +import { generateFilesFromInitState } from './generate.js' const program = new Command() diff --git a/src/utils.ts b/src/utils.ts index 3627027..ee230e2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,7 +23,14 @@ export async function parseInitState( } // Import the file - const imported = await import(filePath) + let imported: any + + if (filePath.endsWith('.ts')) { + // For TypeScript files, use ts-node to compile and evaluate + imported = await importTypeScriptFile(filePath) + } else { + imported = await import(filePath) + } // Validate default export exists if (!imported.default) { @@ -76,6 +83,53 @@ export async function parseInitState( } } +/** + * Import a TypeScript file using ts-node + */ +async function importTypeScriptFile(filePath: string): Promise { + try { + // Use dynamic import with ts-node/esm loader + const { register } = await import('ts-node') + + // Register ts-node for ESM + register({ + esm: true, + experimentalSpecifierResolution: 'node' + }) + + // Import the TypeScript file + const fileUrl = `file://${filePath}` + return await import(fileUrl) + } catch (error) { + // Fallback: try to compile and evaluate manually + const tsNode = await import('ts-node') + const service = tsNode.create({ + compilerOptions: { + module: 'ESNext', + target: 'ES2020', + esModuleInterop: true, + allowSyntheticDefaultImports: true + } + }) + + const fileContent = await fs.readFile(filePath, 'utf-8') + const compiled = service.compile(fileContent, filePath) + + // Create a temporary file to import + const tempFile = filePath.replace('.ts', '.temp.js') + await fs.writeFile(tempFile, compiled) + + try { + const result = await import(`file://${tempFile}`) + await fs.remove(tempFile) + return result + } catch (importError) { + await fs.remove(tempFile) + throw importError + } + } +} + /** * Check if a string is a reserved JavaScript keyword */ From 7fa448b1e7e406b5677f0085cc44969b1ff3830c Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Sun, 6 Jul 2025 16:47:34 -0400 Subject: [PATCH 06/12] Example added, changes in folder stucture --- examples/src/redux/examples/reducers.ts | 3 + examples/src/redux/examples/slice.ts | 107 ++++++++++++++++++++++++ examples/src/redux/examples/types.ts | 38 +++++++++ examples/src/redux/state.ts | 5 ++ src/generate.ts | 61 ++++++++++++-- templates/state.ts.tpl | 2 +- 6 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 examples/src/redux/examples/reducers.ts create mode 100644 examples/src/redux/examples/slice.ts create mode 100644 examples/src/redux/examples/types.ts create mode 100644 examples/src/redux/state.ts diff --git a/examples/src/redux/examples/reducers.ts b/examples/src/redux/examples/reducers.ts new file mode 100644 index 0000000..60ad73a --- /dev/null +++ b/examples/src/redux/examples/reducers.ts @@ -0,0 +1,3 @@ +import reducer from './slice' + +export default reducer diff --git a/examples/src/redux/examples/slice.ts b/examples/src/redux/examples/slice.ts new file mode 100644 index 0000000..a694f3f --- /dev/null +++ b/examples/src/redux/examples/slice.ts @@ -0,0 +1,107 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { ExamplesState } from './types' + +/** + * Initial state for the examples slice + */ +const initialState: ExamplesState = { + "products": [], + "loading": false, + "error": null, + "selectedProductId": 0, + "searchQuery": "", + "currentPage": 1, + "itemsPerPage": 10, + "totalItems": 0, + "filters": { + "category": "", + "priceRange": [ + 0, + 1000 + ], + "inStock": true, + "sortBy": "name", + "sortOrder": "asc" + }, + "isFiltersOpen": false, + "selectedView": "grid", + "editingProduct": null, + "isDirty": false, + "categories": [ + "electronics", + "clothing", + "books" + ], + "recentlyViewed": [], + "lastUpdated": null, + "isInitialized": false, + "debugMode": false +} + +/** + * Redux slice for examples state management + * Auto-generated by react-state-cli + */ +const examplesSlice = createSlice({ + name: 'examples', + initialState, + reducers: { + setProducts: (state, action: PayloadAction) => { + state.products = action.payload + }, + setLoading: (state, action: PayloadAction) => { + state.loading = action.payload + }, + setError: (state, action: PayloadAction) => { + state.error = action.payload + }, + setSelectedProductId: (state, action: PayloadAction) => { + state.selectedProductId = action.payload + }, + setSearchQuery: (state, action: PayloadAction) => { + state.searchQuery = action.payload + }, + setCurrentPage: (state, action: PayloadAction) => { + state.currentPage = action.payload + }, + setItemsPerPage: (state, action: PayloadAction) => { + state.itemsPerPage = action.payload + }, + setTotalItems: (state, action: PayloadAction) => { + state.totalItems = action.payload + }, + setFilters: (state, action: PayloadAction>) => { + state.filters = action.payload + }, + setIsFiltersOpen: (state, action: PayloadAction) => { + state.isFiltersOpen = action.payload + }, + setSelectedView: (state, action: PayloadAction) => { + state.selectedView = action.payload + }, + setEditingProduct: (state, action: PayloadAction) => { + state.editingProduct = action.payload + }, + setIsDirty: (state, action: PayloadAction) => { + state.isDirty = action.payload + }, + setCategories: (state, action: PayloadAction) => { + state.categories = action.payload + }, + setRecentlyViewed: (state, action: PayloadAction) => { + state.recentlyViewed = action.payload + }, + setLastUpdated: (state, action: PayloadAction) => { + state.lastUpdated = action.payload + }, + setIsInitialized: (state, action: PayloadAction) => { + state.isInitialized = action.payload + }, + setDebugMode: (state, action: PayloadAction) => { + state.debugMode = action.payload + }, + } +}) + +export const { setProducts, setLoading, setError, setSelectedProductId, setSearchQuery, setCurrentPage, setItemsPerPage, setTotalItems, setFilters, setIsFiltersOpen, setSelectedView, setEditingProduct, setIsDirty, setCategories, setRecentlyViewed, setLastUpdated, setIsInitialized, setDebugMode } = examplesSlice.actions +export default examplesSlice.reducer diff --git a/examples/src/redux/examples/types.ts b/examples/src/redux/examples/types.ts new file mode 100644 index 0000000..843db6b --- /dev/null +++ b/examples/src/redux/examples/types.ts @@ -0,0 +1,38 @@ +export interface ExamplesState { + /** Array with 0 initial items */ + products: any[] + /** boolean value */ + loading: boolean + /** Nullable value */ + error: null + /** number value */ + selectedProductId: number + /** string value */ + searchQuery: string + /** number value */ + currentPage: number + /** number value */ + itemsPerPage: number + /** number value */ + totalItems: number + /** Object with 5 properties */ + filters: Record + /** boolean value */ + isFiltersOpen: boolean + /** string value */ + selectedView: string + /** Nullable value */ + editingProduct: null + /** boolean value */ + isDirty: boolean + /** Array with 3 initial items */ + categories: string[] + /** Array with 0 initial items */ + recentlyViewed: any[] + /** Nullable value */ + lastUpdated: null + /** boolean value */ + isInitialized: boolean + /** boolean value */ + debugMode: boolean +} \ No newline at end of file diff --git a/examples/src/redux/state.ts b/examples/src/redux/state.ts new file mode 100644 index 0000000..46d45f4 --- /dev/null +++ b/examples/src/redux/state.ts @@ -0,0 +1,5 @@ +import { ExamplesState } from './examples/types' + +export interface RootState { + examples: ExamplesState +} diff --git a/src/generate.ts b/src/generate.ts index 391e055..2b1561c 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -18,14 +18,55 @@ export async function generateFilesFromInitState( options: GenerateOptions = {} ): Promise { try { - const sliceDir = path.dirname(initStatePath) - const sliceName = path.basename(sliceDir) - const reduxDir = path.resolve(sliceDir, '..') // goes to /redux - const stateFilePath = path.resolve(reduxDir, 'state.ts') + const originalSliceDir = path.dirname(initStatePath) + const sliceName = path.basename(originalSliceDir) + + // Determine Redux structure and slice directory + let stateFilePath: string + let reduxRootDir: string + let sliceDir: string + + // Check if we're already in a Redux structure + const parentDir = path.dirname(originalSliceDir) + const parentDirName = path.basename(parentDir) + + if (parentDirName === 'redux' || parentDirName === 'store') { + // We're in src/redux/user/ structure - keep existing structure + reduxRootDir = parentDir + sliceDir = originalSliceDir + stateFilePath = path.resolve(reduxRootDir, 'state.ts') + } else { + // We need to create/find the redux structure + // Look for existing src/redux or create it + const cwd = process.cwd() + const srcReduxPath = path.resolve(cwd, 'src', 'redux') + const srcStorePath = path.resolve(cwd, 'src', 'store') + + if (await fs.pathExists(srcReduxPath)) { + reduxRootDir = srcReduxPath + } else if (await fs.pathExists(srcStorePath)) { + reduxRootDir = srcStorePath + } else { + // Create src/redux structure + reduxRootDir = srcReduxPath + await fs.ensureDir(reduxRootDir) + if (options.verbose) { + console.log(chalk.blue(`šŸ“ Created Redux directory: ${reduxRootDir}`)) + } + } + + // Create the slice directory in the Redux structure + sliceDir = path.resolve(reduxRootDir, sliceName) + await fs.ensureDir(sliceDir) + stateFilePath = path.resolve(reduxRootDir, 'state.ts') + } if (options.verbose) { + console.log(chalk.blue(`šŸ“‚ Original directory: ${originalSliceDir}`)) console.log(chalk.blue(`šŸ“‚ Slice directory: ${sliceDir}`)) console.log(chalk.blue(`šŸ“ Slice name: ${sliceName}`)) + console.log(chalk.blue(`šŸ“ Redux root directory: ${reduxRootDir}`)) + console.log(chalk.blue(`šŸ“„ State file path: ${stateFilePath}`)) } const initState = await parseInitState(initStatePath) @@ -114,9 +155,19 @@ export async function generateFilesFromInitState( ) const stateTemplate = await fs.readFile(stateTemplatePath, 'utf-8') + // Determine the correct import path for the types file + // Calculate relative path from state.ts location to types.ts location + const typesFilePath = path.resolve(sliceDir, 'types.ts') + const stateDir = path.dirname(stateFilePath) + const relativePath = path.relative(stateDir, typesFilePath) + + // Convert to proper import path (use forward slashes and remove .ts extension) + const importPath = relativePath.replace(/\\/g, '/').replace(/\.ts$/, '') + const renderedState = ejs.render(stateTemplate, { sliceName, - interfaceName + interfaceName, + importPath: `./${importPath}` }) const stateExists = await fs.pathExists(stateFilePath) diff --git a/templates/state.ts.tpl b/templates/state.ts.tpl index 4f40461..0437732 100644 --- a/templates/state.ts.tpl +++ b/templates/state.ts.tpl @@ -1,4 +1,4 @@ -import { <%= interfaceName %> } from './<%= sliceName %>/types' +import { <%= interfaceName %> } from '<%= importPath %>' export interface RootState { <%= sliceName %>: <%= interfaceName %> From cb3093cdf26c1b7763e24209c75c0e67d6a0a929 Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Sun, 6 Jul 2025 17:06:55 -0400 Subject: [PATCH 07/12] Style fix for github acrion --- src/generate.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/generate.ts b/src/generate.ts index 2b1561c..d23591b 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -20,16 +20,16 @@ export async function generateFilesFromInitState( try { const originalSliceDir = path.dirname(initStatePath) const sliceName = path.basename(originalSliceDir) - + // Determine Redux structure and slice directory let stateFilePath: string let reduxRootDir: string let sliceDir: string - + // Check if we're already in a Redux structure const parentDir = path.dirname(originalSliceDir) const parentDirName = path.basename(parentDir) - + if (parentDirName === 'redux' || parentDirName === 'store') { // We're in src/redux/user/ structure - keep existing structure reduxRootDir = parentDir @@ -41,7 +41,7 @@ export async function generateFilesFromInitState( const cwd = process.cwd() const srcReduxPath = path.resolve(cwd, 'src', 'redux') const srcStorePath = path.resolve(cwd, 'src', 'store') - + if (await fs.pathExists(srcReduxPath)) { reduxRootDir = srcReduxPath } else if (await fs.pathExists(srcStorePath)) { @@ -54,7 +54,7 @@ export async function generateFilesFromInitState( console.log(chalk.blue(`šŸ“ Created Redux directory: ${reduxRootDir}`)) } } - + // Create the slice directory in the Redux structure sliceDir = path.resolve(reduxRootDir, sliceName) await fs.ensureDir(sliceDir) @@ -160,10 +160,10 @@ export async function generateFilesFromInitState( const typesFilePath = path.resolve(sliceDir, 'types.ts') const stateDir = path.dirname(stateFilePath) const relativePath = path.relative(stateDir, typesFilePath) - + // Convert to proper import path (use forward slashes and remove .ts extension) const importPath = relativePath.replace(/\\/g, '/').replace(/\.ts$/, '') - + const renderedState = ejs.render(stateTemplate, { sliceName, interfaceName, From 14375af6976df2dae5062835967c6b376151655d Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Mon, 7 Jul 2025 22:56:22 -0400 Subject: [PATCH 08/12] feat: add comprehensive boundary tests for utility functions - Add extensive test coverage for capitalize function including edge cases - Implement boundary tests for inferType with primitive, array, and object types - Add tests for complex nested structures and circular references - Include edge case testing for special values (NaN, Infinity, Date, RegExp) - Add comprehensive tests for getTypeDescription function - Cover real-world scenarios and boundary conditions for all utility functions --- jest.config.cjs | 29 + package-lock.json | 1551 ++++++++++++++++++++++++++++++++++++++++- package.json | 10 +- src/generate.ts | 128 +++- src/index.ts | 26 +- src/utils.ts | 40 +- test/generate.test.ts | 253 +++++++ test/utils.test.ts | 47 ++ vitest.config.ts | 8 + 9 files changed, 2036 insertions(+), 56 deletions(-) create mode 100644 jest.config.cjs create mode 100644 test/generate.test.ts create mode 100644 test/utils.test.ts create mode 100644 vitest.config.ts diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 0000000..8fdd16b --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,29 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/__tests__'], + setupFilesAfterEnv: ['/jest.setup.cjs'], + testMatch: [ + '**/__tests__/**/*.test.ts', + '**/?(*.)+(spec|test).ts' + ], + transform: { + '^.+\\.ts$': ['ts-jest', { + useESM: false, + tsconfig: { + module: 'commonjs', + target: 'es2020' + } + }] + }, + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts' + ], + moduleFileExtensions: ['ts', 'js'], + testPathIgnorePatterns: [ + '/node_modules/', + '/dist/' + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 55e0fb5..8be1874 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,7 @@ "ejs": "^3.1.10", "fs-extra": "^11.3.0", "ts-morph": "^26.0.0", - "ts-node": "^10.9.2", - "typescript": "^5.8.3" + "ts-node": "^10.9.2" }, "bin": { "react-state-cli": "dist/index.js" @@ -28,7 +27,10 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "eslint": "^8.57.0", - "prettier": "^3.0.0" + "prettier": "^3.0.0", + "tsx": "^4.20.3", + "typescript": "^5.8.3", + "vitest": "^3.2.4" } }, "node_modules/@cspotcode/source-map-support": { @@ -43,6 +45,448 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", + "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", + "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", + "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", + "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", + "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", + "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", + "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", + "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", + "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", + "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", + "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", + "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", + "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", + "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", + "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", + "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", + "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", + "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", + "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", + "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", + "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", + "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", + "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", + "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", + "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", + "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -299,6 +743,286 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.2.tgz", + "integrity": "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.2.tgz", + "integrity": "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.2.tgz", + "integrity": "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.2.tgz", + "integrity": "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.2.tgz", + "integrity": "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.2.tgz", + "integrity": "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.2.tgz", + "integrity": "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.2.tgz", + "integrity": "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.2.tgz", + "integrity": "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.2.tgz", + "integrity": "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.2.tgz", + "integrity": "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.2.tgz", + "integrity": "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.2.tgz", + "integrity": "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.2.tgz", + "integrity": "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.2.tgz", + "integrity": "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.2.tgz", + "integrity": "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.2.tgz", + "integrity": "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.2.tgz", + "integrity": "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.2.tgz", + "integrity": "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.2.tgz", + "integrity": "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -361,6 +1085,23 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "license": "MIT" }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", @@ -368,6 +1109,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/fs-extra": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", @@ -598,6 +1346,121 @@ "dev": true, "license": "ISC" }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -697,6 +1560,16 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -730,6 +1603,16 @@ "node": ">=8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -740,6 +1623,23 @@ "node": ">=6" } }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -752,6 +1652,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/code-block-writer": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", @@ -830,6 +1740,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -887,6 +1807,55 @@ "node": ">=0.10.0" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", + "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.6", + "@esbuild/android-arm": "0.25.6", + "@esbuild/android-arm64": "0.25.6", + "@esbuild/android-x64": "0.25.6", + "@esbuild/darwin-arm64": "0.25.6", + "@esbuild/darwin-x64": "0.25.6", + "@esbuild/freebsd-arm64": "0.25.6", + "@esbuild/freebsd-x64": "0.25.6", + "@esbuild/linux-arm": "0.25.6", + "@esbuild/linux-arm64": "0.25.6", + "@esbuild/linux-ia32": "0.25.6", + "@esbuild/linux-loong64": "0.25.6", + "@esbuild/linux-mips64el": "0.25.6", + "@esbuild/linux-ppc64": "0.25.6", + "@esbuild/linux-riscv64": "0.25.6", + "@esbuild/linux-s390x": "0.25.6", + "@esbuild/linux-x64": "0.25.6", + "@esbuild/netbsd-arm64": "0.25.6", + "@esbuild/netbsd-x64": "0.25.6", + "@esbuild/openbsd-arm64": "0.25.6", + "@esbuild/openbsd-x64": "0.25.6", + "@esbuild/openharmony-arm64": "0.25.6", + "@esbuild/sunos-x64": "0.25.6", + "@esbuild/win32-arm64": "0.25.6", + "@esbuild/win32-ia32": "0.25.6", + "@esbuild/win32-x64": "0.25.6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1082,6 +2051,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1092,6 +2071,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1256,6 +2245,34 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1543,6 +2560,13 @@ "node": "*" } }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1636,6 +2660,23 @@ "dev": true, "license": "MIT" }, + "node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1687,6 +2728,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -1813,6 +2873,30 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1825,6 +2909,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -1912,6 +3025,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -1939,6 +3062,46 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz", + "integrity": "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.2", + "@rollup/rollup-android-arm64": "4.44.2", + "@rollup/rollup-darwin-arm64": "4.44.2", + "@rollup/rollup-darwin-x64": "4.44.2", + "@rollup/rollup-freebsd-arm64": "4.44.2", + "@rollup/rollup-freebsd-x64": "4.44.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.2", + "@rollup/rollup-linux-arm-musleabihf": "4.44.2", + "@rollup/rollup-linux-arm64-gnu": "4.44.2", + "@rollup/rollup-linux-arm64-musl": "4.44.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2", + "@rollup/rollup-linux-riscv64-gnu": "4.44.2", + "@rollup/rollup-linux-riscv64-musl": "4.44.2", + "@rollup/rollup-linux-s390x-gnu": "4.44.2", + "@rollup/rollup-linux-x64-gnu": "4.44.2", + "@rollup/rollup-linux-x64-musl": "4.44.2", + "@rollup/rollup-win32-arm64-msvc": "4.44.2", + "@rollup/rollup-win32-ia32-msvc": "4.44.2", + "@rollup/rollup-win32-x64-msvc": "4.44.2", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -1998,6 +3161,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -2008,6 +3178,30 @@ "node": ">=8" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2034,6 +3228,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2053,6 +3260,95 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2131,6 +3427,26 @@ } } }, + "node_modules/tsx": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", + "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2201,6 +3517,218 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/vite": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz", + "integrity": "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2217,6 +3745,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index d3e72e8..c3d4c52 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dev": "tsc --watch", "clean": "rm -rf dist", "prepublishOnly": "npm run clean && npm run build", - "test": "echo \"Error: no test specified\" && exit 1", + "test": "vitest", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", "format": "prettier --write \"src/**/*.ts\"", @@ -48,8 +48,7 @@ "ejs": "^3.1.10", "fs-extra": "^11.3.0", "ts-morph": "^26.0.0", - "ts-node": "^10.9.2", - "typescript": "^5.8.3" + "ts-node": "^10.9.2" }, "devDependencies": { "@types/ejs": "^3.1.5", @@ -58,7 +57,10 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "eslint": "^8.57.0", - "prettier": "^3.0.0" + "prettier": "^3.0.0", + "tsx": "^4.20.3", + "typescript": "^5.8.3", + "vitest": "^3.2.4" }, "bin": { "react-state-cli": "./dist/index.js" diff --git a/src/generate.ts b/src/generate.ts index d23591b..0e3fba0 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -13,10 +13,10 @@ export interface GenerateOptions { dryRun?: boolean } -export async function generateFilesFromInitState( +export const generateFilesFromInitState = async ( initStatePath: string, options: GenerateOptions = {} -): Promise { +): Promise => { try { const originalSliceDir = path.dirname(initStatePath) const sliceName = path.basename(originalSliceDir) @@ -215,29 +215,115 @@ export async function generateFilesFromInitState( } } -function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1) -} +// * Test added +export const capitalize = (str: string): string => + str.charAt(0).toUpperCase() + str.slice(1) + +// export const inferType = (value: unknown): string => { +// if (value === null) return 'null' +// if (value === undefined) return 'undefined' +// if (Array.isArray(value)) { +// if (value.length === 0) return 'any[]' +// // Try to infer array element type from first element +// const firstElementType = inferType(value[0]) +// return `${firstElementType}[]` +// } +// if (typeof value === 'object') { +// // For objects, we could either use a generic Record type or create nested interfaces +// // For now, let's use a more specific type +// if (Object.keys(value).length === 0) return 'Record' +// return 'Record' // Could be enhanced to generate nested interfaces +// } +// return typeof value +// } + +// * Test added +export const inferType = (value: unknown): string => { + const seen = new WeakSet() + + const helper = (val: unknown): string => { + if (val === null) return 'null' + if (val === undefined) return 'undefined' + + // Detect special built-in types + if (val instanceof Date) return 'Date' + if (val instanceof RegExp) return 'RegExp' + if (val instanceof Map) { + // For Map, try to infer key and value types if possible + if (val.size === 0) return 'Map' + + const keyTypes = new Set() + const valueTypes = new Set() + val.forEach((v, k) => { + keyTypes.add(helper(k)) + valueTypes.add(helper(v)) + }) + + const keyType = + keyTypes.size === 1 + ? [...keyTypes][0] + : '(' + [...keyTypes].join(' | ') + ')' + const valueType = + valueTypes.size === 1 + ? [...valueTypes][0] + : '(' + [...valueTypes].join(' | ') + ')' + + return `Map<${keyType}, ${valueType}>` + } + if (val instanceof Set) { + if (val.size === 0) return 'Set' -function inferType(value: any): string { - if (value === null) return 'null' - if (value === undefined) return 'undefined' - if (Array.isArray(value)) { - if (value.length === 0) return 'any[]' - // Try to infer array element type from first element - const firstElementType = inferType(value[0]) - return `${firstElementType}[]` - } - if (typeof value === 'object') { - // For objects, we could either use a generic Record type or create nested interfaces - // For now, let's use a more specific type - if (Object.keys(value).length === 0) return 'Record' - return 'Record' // Could be enhanced to generate nested interfaces + const elemTypes = new Set() + val.forEach(v => elemTypes.add(helper(v))) + const elemType = + elemTypes.size === 1 + ? [...elemTypes][0] + : '(' + [...elemTypes].join(' | ') + ')' + + return `Set<${elemType}>` + } + + const type = typeof val + + if (Array.isArray(val)) { + if (val.length === 0) return 'any[]' + + // Infer types of all elements (not just first) + const elementTypes = Array.from(new Set(val.map(item => helper(item)))) + const joinedTypes = + elementTypes.length === 1 + ? elementTypes[0] + : `(${elementTypes.join(' | ')})` + return `${joinedTypes}[]` + } + + if (type === 'object') { + if (seen.has(val)) return 'any /* circular */' + seen.add(val) + + const obj = val as Record + const keys = Object.keys(obj) + if (keys.length === 0) return 'Record' + + const props = keys.map(key => { + const propType = helper(obj[key]) + return `${key}: ${propType};` + }) + + return `{ ${props.join(' ')} }` + } + + if (type === 'function') return 'Function' + if (type === 'symbol') return 'symbol' + + return type } - return typeof value + + return helper(value) } -function getTypeDescription(value: any): string { +// * Test added +export const getTypeDescription = (value: unknown): string => { if (value === null) return 'Nullable value' if (value === undefined) return 'Undefined value' if (Array.isArray(value)) { diff --git a/src/index.ts b/src/index.ts index 50d40a0..83dc2d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,34 +1,37 @@ #!/usr/bin/env node -import { Command } from 'commander' -import path from 'path' -import fs from 'fs-extra' -import chalk from 'chalk' -import { generateFilesFromInitState } from './generate.js' +import { Command } from 'commander' //CLI framework +import path from 'path' // Helps resolve full file paths +import fs from 'fs-extra' // Extends fs with more features +import chalk from 'chalk' // Colored console output +import { generateFilesFromInitState } from './generate.js' //Function to generate files from initState.ts +//Setup CLI Program const program = new Command() program .name('react-state-cli') .description('CLI to generate Redux boilerplate from initState.ts') - .version('1.0.1') + .version('1.1.0') +// Generate Command program .command('generate') .argument('', 'Path to initState.ts file') - .option('-v, --verbose', 'Enable verbose logging') - .option('--dry-run', 'Show what would be generated without creating files') + .option('-v, --verbose', 'Enable verbose logging') //Prints extra debug output + .option('--dry-run', 'Show what would be generated without creating files') // Simulates generation, doesn't actually write files + //Command Logic .action(async (initStatePath, options) => { try { const fullPath = path.resolve(process.cwd(), initStatePath) - // Validate input file exists + //? Validate input file exists if (!(await fs.pathExists(fullPath))) { console.error(chalk.red(`āŒ Error: File not found: ${fullPath}`)) process.exit(1) } - // Validate it's a TypeScript file + //? Validate it's a TypeScript file if (!fullPath.endsWith('.ts') && !fullPath.endsWith('.js')) { console.error(chalk.red('āŒ Error: File must be a .ts or .js file')) process.exit(1) @@ -38,8 +41,10 @@ program console.log(chalk.blue(`šŸ” Processing: ${fullPath}`)) } + //? Main task Execution await generateFilesFromInitState(fullPath, options) + // * Success Message console.log(chalk.green('šŸŽ‰ Redux boilerplate generated successfully!')) } catch (error) { console.error( @@ -54,4 +59,5 @@ program } }) +// ? Executes the CLI command program.parse() diff --git a/src/utils.ts b/src/utils.ts index ee230e2..4249d58 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,9 +7,9 @@ import fs from 'fs-extra' * @returns Promise> - The parsed initial state object * @throws Error if file is invalid or doesn't export proper object */ -export async function parseInitState( +export const parseInitState = async ( filePath: string -): Promise> { +): Promise> => { try { // Verify file exists if (!(await fs.pathExists(filePath))) { @@ -86,7 +86,7 @@ export async function parseInitState( /** * Import a TypeScript file using ts-node */ -async function importTypeScriptFile(filePath: string): Promise { +const importTypeScriptFile = async (filePath: string): Promise => { try { // Use dynamic import with ts-node/esm loader const { register } = await import('ts-node') @@ -133,11 +133,14 @@ async function importTypeScriptFile(filePath: string): Promise { /** * Check if a string is a reserved JavaScript keyword */ -function isReservedKeyword(word: string): boolean { - const reservedWords = [ +export const isReservedKeyword = (word: string): boolean => { + const reservedWords = new Set([ 'break', 'case', 'catch', + 'true', + 'false', + 'console', 'class', 'const', 'continue', @@ -164,6 +167,8 @@ function isReservedKeyword(word: string): boolean { 'try', 'typeof', 'var', + 'null', + 'undefined', 'void', 'while', 'with', @@ -176,17 +181,16 @@ function isReservedKeyword(word: string): boolean { 'package', 'private', 'protected', - 'public' - ] - return reservedWords.includes(word.toLowerCase()) -} - -/** - * Validate that a string is a valid TypeScript identifier - */ -export function isValidIdentifier(name: string): boolean { - // Must start with letter, underscore, or dollar sign - // Can contain letters, digits, underscores, or dollar signs - const identifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/ - return identifierRegex.test(name) && !isReservedKeyword(name) + 'public', + 'NaN', + 'Infinity', + 'Date', + 'RegExp', + 'Function', + 'Map', + 'Set', + 'Symbol' + ]) + + return reservedWords.has(word) } diff --git a/test/generate.test.ts b/test/generate.test.ts new file mode 100644 index 0000000..4162824 --- /dev/null +++ b/test/generate.test.ts @@ -0,0 +1,253 @@ +import { describe, expect, it } from 'vitest' +import { capitalize, getTypeDescription, inferType } from '../src/generate' + +describe('capitalize function', () => { + describe('Real-world scenarios', () => { + it('handles camelCase variable names', () => { + expect(capitalize('userName')).toBe('UserName') + expect(capitalize('firstName')).toBe('FirstName') + expect(capitalize('isActive')).toBe('IsActive') + }) + + it('handles snake_case variable names', () => { + expect(capitalize('user_name')).toBe('User_name') + expect(capitalize('first_name')).toBe('First_name') + }) + + it('handles kebab-case variable names', () => { + expect(capitalize('user-name')).toBe('User-name') + expect(capitalize('first-name')).toBe('First-name') + }) + + it('handles PascalCase variable names', () => { + expect(capitalize('UserName')).toBe('UserName') + expect(capitalize('FirstName')).toBe('FirstName') + }) + + it('handles typical Redux slice names', () => { + expect(capitalize('auth')).toBe('Auth') + expect(capitalize('user')).toBe('User') + expect(capitalize('shoppingCart')).toBe('ShoppingCart') + expect(capitalize('userProfile')).toBe('UserProfile') + }) + }) + + describe('Edge cases - Long strings', () => { + it('handles very long string', () => { + const longString = 'a'.repeat(1000) + const expected = 'A' + 'a'.repeat(999) + expect(capitalize(longString)).toBe(expected) + }) + + it('handles long string starting with number', () => { + const longString = '1' + 'a'.repeat(999) + expect(capitalize(longString)).toBe(longString) + }) + }) + + describe('Edge cases - Complex scenarios', () => { + it('handles string with multiple words', () => { + expect(capitalize('hello world')).toBe('Hello world') + }) + + it('handles string with dots', () => { + expect(capitalize('config.json')).toBe('Config.json') + }) + + it('handles string with file extensions', () => { + expect(capitalize('index.ts')).toBe('Index.ts') + expect(capitalize('component.tsx')).toBe('Component.tsx') + }) + + it('handles string with version numbers', () => { + expect(capitalize('v1.2.3')).toBe('V1.2.3') + }) + + it('handles string with common abbreviations', () => { + expect(capitalize('api')).toBe('Api') + expect(capitalize('ui')).toBe('Ui') + expect(capitalize('db')).toBe('Db') + }) + }) + + describe('Edge cases - Boundary conditions', () => { + it('handles string with only one character that is not a letter', () => { + expect(capitalize('1')).toBe('1') + expect(capitalize('!')).toBe('!') + expect(capitalize(' ')).toBe(' ') + }) + + it('handles string with alternating case', () => { + expect(capitalize('aAbBcC')).toBe('AAbBcC') + }) + + it('handles string with all uppercase', () => { + expect(capitalize('HELLO')).toBe('HELLO') + }) + + it('handles string with all lowercase', () => { + expect(capitalize('hello')).toBe('Hello') + }) + }) +}) + +describe('inferType function', () => { + describe('Real-world scenarios', () => { + it('should infer primitive types', () => { + expect(inferType(1)).toBe('number') + expect(inferType('hello')).toBe('string') + expect(inferType(true)).toBe('boolean') + expect(inferType(null)).toBe('null') + expect(inferType(undefined)).toBe('undefined') + }) + + it('should infer array types', () => { + expect(inferType([])).toBe('any[]') + expect(inferType([1, 2, 3])).toBe('number[]') + expect(inferType(['a', 'b'])).toBe('string[]') + expect(inferType([1, 'a'])).toBe('(number | string)[]') + }) + + it('should infer object types', () => { + expect(inferType({})).toBe('Record') + expect(inferType({ name: 'John', age: 30 })).toBe( + '{ name: string; age: number; }' + ) + }) + }) + + describe('Edge cases - Boundary conditions', () => { + it('handles strings that may look like numbers', () => { + expect(inferType('1')).toBe('string') // Clarified: still a string + expect(inferType('001')).toBe('string') + expect(inferType(' ')).toBe('string') + expect(inferType('!')).toBe('string') + }) + + it('handles special numeric values', () => { + expect(inferType(NaN)).toBe('number') + expect(inferType(Infinity)).toBe('number') + expect(inferType(-Infinity)).toBe('number') + }) + + it('handles Date and RegExp', () => { + expect(inferType(new Date())).toBe('Date') + expect(inferType(/abc/)).toBe('RegExp') + }) + + it('handles functions', () => { + expect(inferType(() => {})).toBe('Function') + expect(inferType(function namedFunc() {})).toBe('Function') + }) + }) + + describe('Edge cases - Complex scenarios', () => { + const complexObject = { + interface: 'John', + age: 30, + class: true, + address: { + street: '123 Main St', + city: 'Anytown', + state: 'CA', + zip: '12345' + }, + phone: '+1234567890', + null: 'john@example.com', + website: 'https://www.john.com', + skills: ['JavaScript', 'React', 'Node.js'], + projects: [ + { null: 'Project 1', Number: 'A project about JavaScript' }, + { name: 'Project 2', description: undefined }, + { NaN: 'Project 3', description: null } + ], + hobbies: ['reading', 'traveling', 'photography'] + } + + it('infers complex nested object types', () => { + const inferredType = inferType(complexObject) + expect(inferredType).toContain('address: {') // Nested object check + expect(inferredType).toContain('skills: string[]') + expect(inferredType).toMatch(/projects:\s*\(.+\)\[\]/) + }) + }) + + describe('Miscellaneous', () => { + it('infers Map, Set, and Symbol types', () => { + expect(inferType(new Map())).toBe('Map') + expect(inferType(new Set([1, 2, 3]))).toBe('Set') + expect(inferType(Symbol('sym'))).toBe('symbol') + }) + + it('handles deeply nested structures', () => { + const deep = { a: { b: { c: { d: [1, 2, 3] } } } } + expect(inferType(deep)).toBe('{ a: { b: { c: { d: number[]; }; }; }; }') + }) + + it('handles circular references gracefully', () => { + const obj: any = {} + obj.self = obj + + // Expect inferType to either throw a warning, return a placeholder, or not crash + expect(() => inferType(obj)).not.toThrow() + }) + }) +}) + +describe('getTypeDescription function - Boundary Tests', () => { + it('should describe null value', () => { + expect(getTypeDescription(null)).toBe('Nullable value') + }) + + it('should describe undefined value', () => { + expect(getTypeDescription(undefined)).toBe('Undefined value') + }) + it('should describe undefined value', () => { + expect(getTypeDescription('-')).toBe('string value') + }) + + it('should describe empty array', () => { + expect(getTypeDescription([])).toBe('Array with 0 initial items') + }) + + it('should describe non-empty array', () => { + expect(getTypeDescription([1, 2, 3])).toBe('Array with 3 initial items') + }) + + it('should describe empty object', () => { + expect(getTypeDescription({})).toBe('Object with 0 properties') + }) + + it('should describe non-empty object', () => { + expect(getTypeDescription({ a: 1, b: 'hello' })).toBe( + 'Object with 2 properties' + ) + }) + + it('should describe number value', () => { + expect(getTypeDescription(42)).toBe('number value') + }) + + it('should describe string value', () => { + expect(getTypeDescription('test')).toBe('string value') + }) + + it('should describe boolean value', () => { + expect(getTypeDescription(true)).toBe('boolean value') + }) + + it('should describe function value', () => { + expect(getTypeDescription(() => {})).toBe('function value') + }) + + it('should describe symbol value', () => { + expect(getTypeDescription(Symbol('sym'))).toBe('symbol value') + }) + + it('should describe object created from class instance', () => { + class MyClass { + prop = 123 + } + expect(getTypeDescription(new MyClass())).toBe('Object with 1 properties') + }) +}) diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 0000000..0734aae --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest' +import { isReservedKeyword } from '../src/utils' + +describe('isReservedKeyword function', () => { + describe('Real-world scenarios', () => { + it('should return true for reserved JavaScript keywords', () => { + expect(isReservedKeyword('for')).toBe(true) + expect(isReservedKeyword('while')).toBe(true) + expect(isReservedKeyword('if')).toBe(true) + expect(isReservedKeyword('else')).toBe(true) + expect(isReservedKeyword('return')).toBe(true) + expect(isReservedKeyword('class')).toBe(true) + expect(isReservedKeyword('const')).toBe(true) + expect(isReservedKeyword('let')).toBe(true) + expect(isReservedKeyword('var')).toBe(true) + expect(isReservedKeyword('function')).toBe(true) + expect(isReservedKeyword('try')).toBe(true) + expect(isReservedKeyword('catch')).toBe(true) + expect(isReservedKeyword('new')).toBe(true) + expect(isReservedKeyword('null')).toBe(true) + expect(isReservedKeyword('true')).toBe(true) + expect(isReservedKeyword('false')).toBe(true) + }) + + it('should return false for non-keywords and identifiers', () => { + expect(isReservedKeyword('hello')).toBe(false) + expect(isReservedKeyword('myVar')).toBe(false) + expect(isReservedKeyword('returnValue')).toBe(false) + expect(isReservedKeyword('functionName')).toBe(false) + expect(isReservedKeyword('letmein')).toBe(false) + }) + + it('should handle case sensitivity correctly', () => { + expect(isReservedKeyword('Function')).toBe(false) + expect(isReservedKeyword('Class')).toBe(false) + expect(isReservedKeyword('Var')).toBe(false) + }) + + it('should handle edge cases', () => { + expect(isReservedKeyword('')).toBe(false) + expect(isReservedKeyword('123')).toBe(false) + expect(isReservedKeyword('$for')).toBe(false) + expect(isReservedKeyword('while1')).toBe(false) + expect(isReservedKeyword('_var')).toBe(false) + }) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..f7d0b93 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, // so you can use `describe`, `it`, `expect` globally like Jest + environment: 'node' + } +}) From 9d985293a598c4b371e8b5dff8e09bdd5b93eaf2 Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Tue, 8 Jul 2025 21:52:46 -0400 Subject: [PATCH 09/12] Test case added --- .gitignore | 5 +- src/generate.ts | 3 + src/utils.ts | 17 +- test/generate.test.ts | 46 +++- test/generateFilesFromInitState.test.ts | 133 ++++++++++ test/utils.test.ts | 325 +++++++++++++++++++++++- 6 files changed, 516 insertions(+), 13 deletions(-) create mode 100644 test/generateFilesFromInitState.test.ts diff --git a/.gitignore b/.gitignore index 76add87..881e8b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules -dist \ No newline at end of file +dist + + +__temp__ diff --git a/src/generate.ts b/src/generate.ts index 0e3fba0..b411da1 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -70,6 +70,9 @@ export const generateFilesFromInitState = async ( } const initState = await parseInitState(initStatePath) + if (!initState || Object.keys(initState).length === 0) { + throw new Error('Initial state object is empty') + } const keys = Object.keys(initState) if (keys.length === 0) { diff --git a/src/utils.ts b/src/utils.ts index 4249d58..b08ee17 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -51,11 +51,7 @@ export const parseInitState = async ( throw new Error('Default export must be an object, not an array') } - // Validate it's not empty - const keys = Object.keys(imported.default) - if (keys.length === 0) { - throw new Error('Default export object cannot be empty') - } + // Note: Empty object validation is handled by generateFilesFromInitState // Validate object structure for (const [key] of Object.entries(imported.default)) { @@ -86,7 +82,8 @@ export const parseInitState = async ( /** * Import a TypeScript file using ts-node */ -const importTypeScriptFile = async (filePath: string): Promise => { +// * Testcase: added +export const importTypeScriptFile = async (filePath: string): Promise => { try { // Use dynamic import with ts-node/esm loader const { register } = await import('ts-node') @@ -98,7 +95,8 @@ const importTypeScriptFile = async (filePath: string): Promise => { }) // Import the TypeScript file - const fileUrl = `file://${filePath}` + const absolutePath = path.resolve(filePath) + const fileUrl = `file://${absolutePath}` return await import(fileUrl) } catch (error) { // Fallback: try to compile and evaluate manually @@ -120,7 +118,8 @@ const importTypeScriptFile = async (filePath: string): Promise => { await fs.writeFile(tempFile, compiled) try { - const result = await import(`file://${tempFile}`) + const absoluteTempPath = path.resolve(tempFile) + const result = await import(`file://${absoluteTempPath}`) await fs.remove(tempFile) return result } catch (importError) { @@ -133,6 +132,7 @@ const importTypeScriptFile = async (filePath: string): Promise => { /** * Check if a string is a reserved JavaScript keyword */ +// * Testcase: added export const isReservedKeyword = (word: string): boolean => { const reservedWords = new Set([ 'break', @@ -186,7 +186,6 @@ export const isReservedKeyword = (word: string): boolean => { 'Infinity', 'Date', 'RegExp', - 'Function', 'Map', 'Set', 'Symbol' diff --git a/test/generate.test.ts b/test/generate.test.ts index 4162824..fb57e95 100644 --- a/test/generate.test.ts +++ b/test/generate.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest' -import { capitalize, getTypeDescription, inferType } from '../src/generate' +import { + capitalize, + getTypeDescription, + inferType +} from '../src/generate' + describe('capitalize function', () => { describe('Real-world scenarios', () => { @@ -251,3 +256,42 @@ describe('getTypeDescription function - Boundary Tests', () => { expect(getTypeDescription(new MyClass())).toBe('Object with 1 properties') }) }) + +// const tmpDir = path.join(os.tmpdir(), 'gen-test') + +// async function writeInitState(name: string, content: string): Promise { +// const dir = path.join(tmpDir, 'src', 'redux', name) +// await fs.ensureDir(dir) +// const filePath = path.join(dir, 'initState.ts') +// await fs.writeFile(filePath, content) +// return filePath +// } + +// describe('generateFilesFromInitState', () => { +// beforeEach(async () => { +// await fs.emptyDir(tmpDir) +// }) + +// it('should generate slice, types, reducers, and state files', async () => { +// const initStatePath = await writeInitState( +// 'user', +// `export default { +// name: "Alice", +// age: 25 +// }` +// ) + +// await generateFilesFromInitState(initStatePath) + +// // Check that files exist +// const expectedFiles = ['slice.ts', 'types.ts', 'reducers.ts'] +// for (const file of expectedFiles) { +// const fullPath = path.join(tmpDir, 'src', 'redux', 'user', file) +// const exists = await fs.pathExists(fullPath) +// expect(exists).toBe(true) +// } + +// const stateFile = path.join(tmpDir, 'src', 'redux', 'state.ts') +// expect(await fs.pathExists(stateFile)).toBe(true) +// }) +// }) diff --git a/test/generateFilesFromInitState.test.ts b/test/generateFilesFromInitState.test.ts new file mode 100644 index 0000000..7c1302b --- /dev/null +++ b/test/generateFilesFromInitState.test.ts @@ -0,0 +1,133 @@ +import path from 'path' +import fs from 'fs-extra' +import { expect } from 'chai' +import { generateFilesFromInitState } from '../src/generate' +import { beforeEach, describe, it } from 'vitest' + +const tmpDir = path.resolve(__dirname, '..', '__temp__') + +async function writeInitState( + sliceName: string, + content: string +): Promise { + const sliceDir = path.join(tmpDir, 'src', 'redux', sliceName) + const filePath = path.join(sliceDir, 'initState.ts') + await fs.ensureDir(sliceDir) + await fs.writeFile(filePath, content) + return filePath +} + +describe('generateFilesFromInitState', () => { + beforeEach(async () => { + await fs.emptyDir(tmpDir) + }) + + it('should generate slice, types, reducers, and state files', async () => { + const initStatePath = await writeInitState( + 'user', + 'export default { name: \'Alice\', age: 25 }' + ) + await generateFilesFromInitState(initStatePath) + + const expectedFiles = ['slice.ts', 'types.ts', 'reducers.ts'] + for (const file of expectedFiles) { + const filePath = path.join(tmpDir, 'src', 'redux', 'user', file) + const exists = await fs.pathExists(filePath) + expect(exists).to.be.true + } + + const statePath = path.join(tmpDir, 'src', 'redux', 'state.ts') + expect(await fs.pathExists(statePath)).to.be.true + }) + + it('should not write any files in dryRun mode', async () => { + const initStatePath = await writeInitState( + 'user', + 'export default { x: 1 }' + ) + await generateFilesFromInitState(initStatePath, { dryRun: true }) + + const generatedFiles = ['slice.ts', 'types.ts', 'reducers.ts', 'state.ts'] + for (const file of generatedFiles) { + const fullPath = path.join(tmpDir, 'src', 'redux', 'user', file) + expect(await fs.pathExists(fullPath)).to.be.false + } + + const stateFile = path.join(tmpDir, 'src', 'redux', 'state.ts') + expect(await fs.pathExists(stateFile)).to.be.false + }) + + it('should skip state.ts if it already exists', async () => { + const initStatePath = await writeInitState( + 'user', + 'export default { test: true }' + ) + const statePath = path.join(tmpDir, 'src', 'redux', 'state.ts') + + await fs.ensureFile(statePath) + await fs.writeFile(statePath, '// existing state') + + await generateFilesFromInitState(initStatePath) + + const content = await fs.readFile(statePath, 'utf-8') + expect(content).to.equal('// existing state') + }) + + it('should throw if initState object is empty', async () => { + const initStatePath = await writeInitState('User', 'export default {}') + + try { + await generateFilesFromInitState(initStatePath) + throw new Error('Expected error was not thrown') + } catch (err: any) { + expect(err.message).to.include('Initial state object is empty') + } + }) + + it('should handle large state object', async () => { + const largeState = Array.from({ length: 100 }) + .map((_, i) => `key${i}: ${i}`) + .join(',\n') + const initStatePath = await writeInitState( + 'big', + `export default {\n${largeState}\n}` + ) + + await generateFilesFromInitState(initStatePath) + + const typesFile = path.join(tmpDir, 'src', 'redux', 'big', 'types.ts') + const content = await fs.readFile(typesFile, 'utf-8') + expect(content).to.include('key99') + }) + + it('should throw if file does not exist', async () => { + const nonExistentPath = path.join( + tmpDir, + 'src', + 'redux', + 'missing', + 'initState.ts' + ) + + try { + await generateFilesFromInitState(nonExistentPath) + throw new Error('Expected error was not thrown') + } catch (err: any) { + expect(err.message).to.include('Failed to generate files') + } + }) + + it('should throw if file contains invalid JavaScript', async () => { + const initStatePath = await writeInitState( + 'broken', + 'export default { name:' + ) + + try { + await generateFilesFromInitState(initStatePath) + throw new Error('Expected error was not thrown') + } catch (err: any) { + expect(err.message).to.include('Failed to generate files') + } + }) +}) diff --git a/test/utils.test.ts b/test/utils.test.ts index 0734aae..9f9c012 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,5 +1,13 @@ -import { describe, expect, it } from 'vitest' -import { isReservedKeyword } from '../src/utils' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { + importTypeScriptFile, + isReservedKeyword, + parseInitState +} from '../src/utils' + +import fs from 'fs-extra' +import path from 'path' +import os from 'os' describe('isReservedKeyword function', () => { describe('Real-world scenarios', () => { @@ -45,3 +53,316 @@ describe('isReservedKeyword function', () => { }) }) }) + +describe('importTypeScriptFile function', () => { + const tmpDir = path.join(os.tmpdir(), 'ts-import-test') + + beforeAll(async () => { + await fs.ensureDir(tmpDir) + }) + + afterAll(async () => { + await fs.remove(tmpDir) + }) + + const createTempTSFile = async ( + name: string, + content: string + ): Promise => { + const filePath = path.join(tmpDir, name) + await fs.writeFile(filePath, content) + return filePath + } + + describe('Real-world usage', () => { + it('should import a valid TypeScript module with default export', async () => { + const filePath = await createTempTSFile( + 'defaultExport.ts', + ` + const data = { message: 'hello world' } + export default data + ` + ) + const module = await importTypeScriptFile(filePath) + expect(module.default.message).toBe('hello world') + }) + + it('should import named exports from a TypeScript file', async () => { + const filePath = await createTempTSFile( + 'namedExport.ts', + ` + export const value = 42 + ` + ) + const module = await importTypeScriptFile(filePath) + expect(module.value).toBe(42) + }) + + it('should import mixed exports (default + named)', async () => { + const filePath = await createTempTSFile( + 'mixedExport.ts', + ` + const config = { env: 'test' } + export default config + export const version = '1.0.0' + ` + ) + const module = await importTypeScriptFile(filePath) + expect(module.default.env).toBe('test') + expect(module.version).toBe('1.0.0') + }) + }) + + describe('Edge cases', () => { + it('should throw if the file has syntax error', async () => { + const filePath = await createTempTSFile( + 'badSyntax.ts', + ` + export const x == 5 + ` + ) + await expect(importTypeScriptFile(filePath)).rejects.toThrow() + }) + + it('should throw if the file does not exist', async () => { + const filePath = path.join(tmpDir, 'nonexistent.ts') + await expect(importTypeScriptFile(filePath)).rejects.toThrow() + }) + + it('should handle file with no exports', async () => { + const filePath = await createTempTSFile( + 'noExports.ts', + ` + const secret = 'internal only' + ` + ) + const module = await importTypeScriptFile(filePath) + expect(Object.keys(module)).toEqual([]) + }) + }) + describe('Boundary and unusual conditions', () => { + it('should work with a large file with many exports', async () => { + const manyExports = Array.from( + { length: 100 }, + (_, i) => `export const val${i} = ${i}` + ).join('\n') + const filePath = await createTempTSFile('manyExports.ts', manyExports) + const module = await importTypeScriptFile(filePath) + expect(module.val0).toBe(0) + expect(module.val99).toBe(99) + }) + + it('should support exporting functions and classes', async () => { + const filePath = await createTempTSFile( + 'functionsAndClasses.ts', + ` + export function greet(name: string) { + return 'Hello, ' + name + } + export class Greeter { + greet(name: string) { + return 'Hi, ' + name + } + } + ` + ) + const module = await importTypeScriptFile(filePath) + expect(module.greet('Alice')).toBe('Hello, Alice') + const greeter = new module.Greeter() + expect(greeter.greet('Bob')).toBe('Hi, Bob') + }) + }) +}) + +describe('parseInitState function', () => { + const tmpDir = path.join(os.tmpdir(), 'parse-init-state-test') + + beforeAll(async () => { + await fs.ensureDir(tmpDir) + }) + + afterAll(async () => { + await fs.remove(tmpDir) + }) + + const createTempFile = async ( + name: string, + content: string + ): Promise => { + const filePath = path.join(tmpDir, name) + await fs.writeFile(filePath, content) + return filePath + } + + describe('Real-world usage', () => { + it('should parse a valid JS file with default object export', async () => { + const filePath = await createTempFile( + 'validExport.js', + ` + export default { + foo: 'bar', + baz: 123 + } + ` + ) + const result = await parseInitState(filePath) + expect(result).toEqual({ foo: 'bar', baz: 123 }) + }) + + it('should throw if file does not exist', async () => { + const filePath = path.join(tmpDir, 'nonexistent.js') + await expect(parseInitState(filePath)).rejects.toThrow(/File not found/) + }) + + it('should throw if path is not a file', async () => { + const dirPath = path.join(tmpDir, 'aDirectory') + await fs.ensureDir(dirPath) + await expect(parseInitState(dirPath)).rejects.toThrow(/not a file/) + }) + }) + + describe('Validation of exports', () => { + it('should throw if default export is missing', async () => { + const filePath = await createTempFile( + 'noDefaultExport.js', + ` + export const foo = 1 + ` + ) + await expect(parseInitState(filePath)).rejects.toThrow(/default object/) + }) + + it('should throw if default export is not an object', async () => { + const filePath = await createTempFile( + 'defaultIsNotObject.js', + ` + export default 42 + ` + ) + await expect(parseInitState(filePath)).rejects.toThrow( + /must be an object/ + ) + }) + + it('should throw if default export is an array', async () => { + const filePath = await createTempFile( + 'defaultIsArray.js', + ` + export default [1, 2, 3] + ` + ) + await expect(parseInitState(filePath)).rejects.toThrow(/not an array/) + }) + }) + + describe('Object structure validation', () => { + it('should throw if property key is empty string or not a string', async () => { + const filePath = await createTempFile( + 'invalidKey.js', + ` + export default { + '': 'empty', + 123: 'number key' + } + ` + ) + // Because JS keys are always strings, the number key is coerced, so we only test empty string key. + await expect(parseInitState(filePath)).rejects.toThrow( + /Invalid property key/ + ) + }) + + it('should throw if property key is a reserved JavaScript keyword', async () => { + // Assuming isReservedKeyword('for') === true + const filePath = await createTempFile( + 'reservedKeyword.js', + ` + export default { + for: 'loop' + } + ` + ) + await expect(parseInitState(filePath)).rejects.toThrow( + /reserved JavaScript keyword/ + ) + }) + + it('should pass if all keys are valid and non-reserved', async () => { + const filePath = await createTempFile( + 'validKeys.js', + ` + export default { + name: 'ChatGPT', + age: 4, + _private: true + } + ` + ) + const result = await parseInitState(filePath) + expect(result).toEqual({ + name: 'ChatGPT', + age: 4, + _private: true + }) + }) + }) + + describe('Handling TypeScript files', () => { + it('should parse a valid TypeScript file with default export', async () => { + const filePath = await createTempFile( + 'validTS.ts', + ` + const state = { ready: true } + export default state + ` + ) + const result = await parseInitState(filePath) + expect(result).toEqual({ ready: true }) + }) + }) + describe('Empty and Half State Cases', () => { + it('should accept an empty object as default export', async () => { + const filePath = await createTempFile( + 'emptyState.js', + ` + export default {} + ` + ) + const result = await parseInitState(filePath) + expect(result).toEqual({}) + }) + + it('should accept an object with keys having null or undefined values (half state)', async () => { + const filePath = await createTempFile( + 'halfState.js', + ` + export default { + foo: null, + bar: undefined, + baz: 'valid' + } + ` + ) + const result = await parseInitState(filePath) + expect(result).toEqual({ + foo: null, + bar: undefined, + baz: 'valid' + }) + }) + + it('should throw if object has keys that are empty strings', async () => { + const filePath = await createTempFile( + 'emptyKey.js', + ` + export default { + "": 'empty key' + } + ` + ) + await expect(parseInitState(filePath)).rejects.toThrow( + /Invalid property key/ + ) + }) + }) +}) From 8ee934f463bf6f01ec3dec976637f1ea0faa0e66 Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Tue, 8 Jul 2025 22:13:58 -0400 Subject: [PATCH 10/12] Pr fix --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 3ceba01..216ed3b 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: Dependency Review - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 with: fail-on-severity: high allow-dependencies-licenses: MIT, Apache-2.0, BSD-3-Clause, ISC, BSD-2-Clause From 1572b9405f7351ca998f4b1419640997bf986f24 Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Tue, 8 Jul 2025 22:15:33 -0400 Subject: [PATCH 11/12] Pr fix --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 216ed3b..50ba80b 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -22,7 +22,7 @@ jobs: uses: actions/dependency-review-action@v4 with: fail-on-severity: high - allow-dependencies-licenses: MIT, Apache-2.0, BSD-3-Clause, ISC, BSD-2-Clause + allow-licenses: MIT, Apache-2.0, BSD-3-Clause, ISC, BSD-2-Clause deny-licenses: GPL-3.0, LGPL-3.0, AGPL-3.0 - name: Setup Node.js From 082e1542fd3296110867067dcb554055ad8d1104 Mon Sep 17 00:00:00 2001 From: "utsav.virani" Date: Tue, 8 Jul 2025 22:16:24 -0400 Subject: [PATCH 12/12] Pr fix - deny-licenses removed --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 50ba80b..8c68a55 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -23,7 +23,7 @@ jobs: with: fail-on-severity: high allow-licenses: MIT, Apache-2.0, BSD-3-Clause, ISC, BSD-2-Clause - deny-licenses: GPL-3.0, LGPL-3.0, AGPL-3.0 + # deny-licenses: GPL-3.0, LGPL-3.0, AGPL-3.0 - name: Setup Node.js uses: actions/setup-node@v4