Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb9f717d54 | |||
| 70be11894c | |||
| 89ab63b153 | |||
| 44c193d4a8 | |||
| 44d259a0ae | |||
| f0adff8784 | |||
| fb453e62c3 | |||
| 001721a8e9 | |||
| b191464ff9 | |||
| 4d7eaa238f | |||
| 601e0d1063 | |||
| 4bb1a2f8c7 | |||
| b506bf8785 | |||
| d5fbeb3fc6 | |||
| 2ecdeff3dc | |||
| 5a663ae767 | |||
| 218c84a39b | |||
| 27d5cdca35 | |||
| 3ebf072bfb | |||
| 08f56ae0a4 | |||
| b2d2684895 | |||
| 1b328c3045 | |||
| f444a04876 | |||
| 6bd2d35992 | |||
| d038b1d681 | |||
| d60de5cef7 | |||
| e1076a60ad |
@@ -6,19 +6,19 @@ on:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${-{gitea.repository_owner}-}:${-{secrets.GITEA_TOKEN}-}@{{module.githost}}/${-{gitea.repository}-}.git
|
||||
NPMCI_TOKEN_NPM: ${-{secrets.NPMCI_TOKEN_NPM}-}
|
||||
NPMCI_TOKEN_NPM2: ${-{secrets.NPMCI_TOKEN_NPM2}-}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${-{secrets.NPMCI_GIT_GITHUBTOKEN}-}
|
||||
NPMCI_URL_CLOUDLY: ${-{secrets.NPMCI_URL_CLOUDLY}-}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
image: ${-{ env.IMAGE }-}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -44,11 +44,11 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
if: ${-{ always() }-}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
image: ${-{ env.IMAGE }-}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -6,19 +6,19 @@ on:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${-{gitea.repository_owner}-}:${-{secrets.GITEA_TOKEN}-}@{{module.githost}}/${-{gitea.repository}-}.git
|
||||
NPMCI_TOKEN_NPM: ${-{secrets.NPMCI_TOKEN_NPM}-}
|
||||
NPMCI_TOKEN_NPM2: ${-{secrets.NPMCI_TOKEN_NPM2}-}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${-{secrets.NPMCI_GIT_GITHUBTOKEN}-}
|
||||
NPMCI_URL_CLOUDLY: ${-{secrets.NPMCI_URL_CLOUDLY}-}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
image: ${-{ env.IMAGE }-}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -42,11 +42,11 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
if: ${-{ always() }-}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
image: ${-{ env.IMAGE }-}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
image: ${-{ env.IMAGE }-}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
image: ${-{ env.IMAGE }-}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,8 +3,6 @@
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
test/
|
||||
test2/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
@@ -18,6 +16,10 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
#------# custom
|
||||
.serena
|
||||
test-output.json
|
||||
|
||||
149
changelog.md
149
changelog.md
@@ -1,6 +1,113 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-12-15 - 2.12.2 - fix(cli)
|
||||
noop: no changes
|
||||
|
||||
- No source or documentation changes detected in the diff; nothing to release.
|
||||
- Package version remains unchanged at 2.12.1.
|
||||
|
||||
## 2025-12-15 - 2.12.1 - fix(cli)
|
||||
No changes detected — no version bump required
|
||||
|
||||
- Current package version: 2.12.0
|
||||
- No files changed in this commit
|
||||
- No release or version bump necessary
|
||||
|
||||
## 2025-12-15 - 2.12.0 - feat(ci,test)
|
||||
feat(ci/test): add test scaffold, GitLab CI, update gitea workflows and .gitignore
|
||||
|
||||
- Add comprehensive test/ scaffold including sample tests, test package.json, npmextra.json, test fixtures and TypeScript test sources
|
||||
- Add GitLab CI pipeline (test/.gitlab-ci.yml), test-specific .npmrc, VSCode launch/settings and supporting qenv/readme files for CI/local test runs
|
||||
- Update .gitea workflow YAML files to use templated placeholders and corrected container image path for CI execution
|
||||
- Update .gitignore to exclude AI assistant and tooling dirs (.claude/, .serena/) and add test/.gitignore to ignore test artifacts
|
||||
- Update changelog and documentation files to reflect recent formatter/commit/service changes and the new test/CI additions
|
||||
|
||||
## 2025-12-15 - 2.11.1 - fix(mod_format/formatters)
|
||||
|
||||
fix(packagejson.formatter): correctly parse scoped package dependency arguments and default to latest
|
||||
|
||||
- Handle scoped packages (e.g. @scope/name@version) by detecting the last '@' after the scope slash so package name and version are split correctly.
|
||||
- Fallback to 'latest' when no version is provided.
|
||||
- Fixes earlier incorrect splitting on every '@' which broke scoped package names.
|
||||
|
||||
## 2025-12-15 - 2.11.0 - feat(mod_format)
|
||||
|
||||
feat(mod_format): use unified diff formatter with filenames and context in BaseFormatter.displayDiff
|
||||
|
||||
- Replaced plugins.smartdiff.formatLineDiffForConsole(...) with plugins.smartdiff.formatUnifiedDiffForConsole(...) when both before and after are present.
|
||||
- Passes originalFileName and revisedFileName as diff.path and sets context to 3 to show a unified diff with surrounding lines.
|
||||
- Improves console output for multi-line diffs by using unified diff format and including file names.
|
||||
|
||||
## 2025-12-15 - 2.10.0 - feat(mod_format)
|
||||
|
||||
Refactor formatting modules to new BaseFormatter and implement concrete analyze/apply logic
|
||||
|
||||
- Replace generic LegacyFormatter with explicit BaseFormatter implementations for formatters: copy, gitignore, license, npmextra, packagejson, prettier, readme, templates, tsconfig (legacy.formatter.ts removed).
|
||||
- Copy formatter: implemented pattern-based copying, template-preserve path handling, content equality check and planned change generation/apply.
|
||||
- Gitignore formatter: canonical template with preservation of custom section when updating/creating .gitignore.
|
||||
- License formatter: added runtime license check against node_modules for incompatible licenses and reporting (no file changes).
|
||||
- Npmextra formatter: automatic migrations for old namespace keys to package-scoped keys and migration of npmAccessLevel -> @git.zone/cli.release.accessLevel; reformatting and interactive prompting to fill missing repo metadata.
|
||||
- Package.json formatter: enforces repository/metadata, sets module type/private/license/scripts/files, ensures/updates dependencies (including fetching latest via registry), and applies pnpm overrides from assets.
|
||||
- Prettier formatter: added check() to compute diffs by running Prettier and returning per-file before/after diffs.
|
||||
- Readme formatter: create readme.md and readme.hints.md when missing with default content.
|
||||
- Templates formatter: apply templates from templatesDir based on project type (vscode, CI, docker, website/service/wcc), compare template vs destination and create/modify files as needed; ensures dest directories exist.
|
||||
- Tsconfig formatter: sets compilerOptions.baseUrl and computes path mappings from @git.zone/tspublish modules.
|
||||
- General: extensive use of plugins (smartfs, path, smartnpm, smartinteract, smartobject, smartlegal), improved logging and verbose messages.
|
||||
|
||||
## 2025-12-15 - 2.9.0 - feat(format)
|
||||
|
||||
Add --diff option to format command to display file diffs; pass flag through CLI and show formatter diffs. Bump @git.zone/tsdoc to ^1.11.0.
|
||||
|
||||
- Add a diff boolean option to mod_format to enable showing file diffs during format operations.
|
||||
- CLI change: pass argvArg.diff into the options so the --diff flag is honored by the format command.
|
||||
- When diff is enabled, run formatter.check() for each active formatter and call displayAllDiffs() for those with differences, with informational logging.
|
||||
- Update dependency @git.zone/tsdoc from ^1.10.2 to ^1.11.0.
|
||||
|
||||
## 2025-12-15 - 2.8.0 - feat(commit)
|
||||
|
||||
Add commit configuration and automatic pre-commit tests
|
||||
|
||||
- Add CommitConfig class to manage @git.zone/cli.commit settings in npmextra.json (alwaysTest, alwaysBuild).
|
||||
- Export CommitConfig from mod_config for use by the CLI.
|
||||
- Add 'gitzone config commit' subcommand with interactive and direct-setting modes (alwaysTest, alwaysBuild).
|
||||
- Merge CLI flags and npmextra config: -t/--test and -b/--build now respect commit.alwaysTest and commit.alwaysBuild.
|
||||
- Run 'pnpm test' early in the commit flow when tests are enabled; abort the commit on failing tests and log results.
|
||||
- Update commit UI/plan to show the test option and include the test step when enabled.
|
||||
- Add 'gitzone config services' entry to configure services via ServiceManager.
|
||||
|
||||
## 2025-12-14 - 2.7.0 - feat(mod_format)
|
||||
|
||||
Add check-only formatting with interactive diff preview; make formatting default to dry-run and extend formatting API
|
||||
|
||||
- Add BaseFormatter.check(), displayDiff() and displayAllDiffs() to compute and render diffs without applying changes.
|
||||
- Extend runFormatter API with new options: write (use to apply changes), checkOnly (only check for diffs), and showDiff (display diffs). When checkOnly is used, runFormatter returns an ICheckResult.
|
||||
- Change default formatting behavior to dry-run. Use --write / -w to actually apply changes. CLI format command updated to respect --write/-w.
|
||||
- Add formatNpmextraWithDiff in mod_config to preview diffs for npmextra.json and prompt the user before applying changes; calls to add/remove/clear registries and set access level now use this preview flow.
|
||||
- Project.fromCwd now accepts an options object ({ requireProjectType?: boolean }) so callers can skip the projectType requirement when appropriate; runFormatter no longer requires projectType for certain formatters.
|
||||
- Introduce a list of formatters that don't require projectType: npmextra, prettier, cleanup, packagejson.
|
||||
- Export the ICheckResult type from the formatter module and update mod_format interfaces to include ICheckResult.
|
||||
- Bump dependency @push.rocks/smartdiff to ^1.1.0.
|
||||
|
||||
## 2025-12-14 - 2.6.1 - fix(npmextra)
|
||||
|
||||
Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration
|
||||
|
||||
- Move TSDoc legal text into a top-level "tsdoc.legal" property in npmextra.json
|
||||
- Remove the misplaced "gitzone" object that previously contained the TSDoc legal block
|
||||
- Reorder and properly nest the "@git.zone/cli" configuration to follow the new "tsdoc" section
|
||||
- Pure configuration change (JSON structure) — no functional code changes
|
||||
|
||||
## 2025-12-14 - 2.6.0 - feat(mod_commit)
|
||||
|
||||
Add execution plan output to commit command
|
||||
|
||||
- Print an execution plan at the start of the commit flow (shows active options and planned steps)
|
||||
- New printExecutionPlan(options) added to ts/mod_commit/mod.ui.ts
|
||||
- Commit command now calls ui.printExecutionPlan(...) before running format or other steps
|
||||
- Execution plan reflects flags: auto-accept (-y), push (-p), build (-b), release (-r), --format, and target registries
|
||||
|
||||
## 2025-12-14 - 2.5.0 - feat(mod_standard)
|
||||
|
||||
Add interactive main menu and help to standard CLI module; route commands via dynamic imports
|
||||
|
||||
- Introduce interactive CLI menu using @push.rocks/smartinteract to prompt user for actions.
|
||||
@@ -9,6 +116,7 @@ Add interactive main menu and help to standard CLI module; route commands via dy
|
||||
- Remove previous static template listing and logger.warn placeholder.
|
||||
|
||||
## 2025-12-14 - 2.4.0 - feat(cli)
|
||||
|
||||
Add optional build step to release flow and auto-format npmextra config when registries change
|
||||
|
||||
- Introduce a --build/-b flag in the commit/release flow to run 'pnpm build' before pushing/releases
|
||||
@@ -19,6 +127,7 @@ Add optional build step to release flow and auto-format npmextra config when reg
|
||||
- Add npmextra registry config entry (https://verdaccio.lossless.digital) to npmextra.json
|
||||
|
||||
## 2025-12-14 - 2.3.0 - feat(config)
|
||||
|
||||
Add interactive menu and help to config command, handle unknown commands, and bump dependencies
|
||||
|
||||
- When running the 'config' command with no arguments, show an interactive menu (via SmartInteract) to choose actions (show, add, remove, clear, access, help) instead of defaulting to 'show'.
|
||||
@@ -27,6 +136,7 @@ Add interactive menu and help to config command, handle unknown commands, and bu
|
||||
- Update dependency: @push.rocks/smartjson -> ^6.0.0.
|
||||
|
||||
## 2025-12-04 - 2.2.1 - fix(commit)
|
||||
|
||||
Prevent auto-accept for BREAKING CHANGE commits; require manual confirmation and warn when --yes is used
|
||||
|
||||
- Do not auto-accept AI commit recommendations when the suggested change is a BREAKING CHANGE (major bump).
|
||||
@@ -35,6 +145,7 @@ Prevent auto-accept for BREAKING CHANGE commits; require manual confirmation and
|
||||
- Introduced isBreakingChange and canAutoAccept flags to centralize the auto-accept logic.
|
||||
|
||||
## 2025-12-02 - 2.2.0 - feat(services)
|
||||
|
||||
Improve services manager and configuration; switch test templates to @git.zone/tstest; bump dev dependencies and update docs
|
||||
|
||||
- services: Add robust ServiceConfiguration (creates .nogit/env.json with sane defaults, syncs ports from existing Docker containers, validates and can reconfigure ports)
|
||||
@@ -45,6 +156,7 @@ Improve services manager and configuration; switch test templates to @git.zone/t
|
||||
- docs: README updates — add issue reporting/security section, AI-powered commit recommendation notes, and clarify trademark/legal wording
|
||||
|
||||
## 2025-11-29 - 2.1.0 - feat(mod_services)
|
||||
|
||||
Add global service registry and global commands for managing project containers
|
||||
|
||||
- Introduce GlobalRegistry class to track registered projects, their containers, ports and last activity (ts/mod_services/classes.globalregistry.ts)
|
||||
@@ -54,30 +166,34 @@ Add global service registry and global commands for managing project containers
|
||||
- Bump dependency @push.rocks/smartfile to ^13.1.0 in package.json
|
||||
|
||||
## 2025-11-27 - 2.0.0 - BREAKING CHANGE(core)
|
||||
|
||||
Migrate filesystem to smartfs (async) and add Elasticsearch service support; refactor format/commit/meta modules
|
||||
|
||||
- Replace @push.rocks/smartfile usage with @push.rocks/smartfs across the codebase; all filesystem operations are now async (SmartFs.file(...).read()/write(), SmartFs.directory(...).list()/create()/delete(), etc.)
|
||||
- Convert formerly synchronous helpers and APIs to async (notable: detectProjectType, getProjectName, readCurrentVersion and related version bumping logic). Callers updated accordingly.
|
||||
- Add Elasticsearch support to services: new config fields (ELASTICSEARCH_*), Docker run/start/stop/logs/status handling, and ELASTICSEARCH_URL in service configuration.
|
||||
- Add Elasticsearch support to services: new config fields (ELASTICSEARCH\_\*), Docker run/start/stop/logs/status handling, and ELASTICSEARCH_URL in service configuration.
|
||||
- Refactor formatting subsystem: cache and rollback/backup systems removed/disabled for stability, format planner execution simplified (sequential), diff/stats reporting updated to use smartfs.
|
||||
- Update package.json dependencies: bump @git.zone/tsbuild, tsrun, tstest; upgrade @push.rocks/smartfile to v13 and add @push.rocks/smartfs dependency; update @types/node.
|
||||
- Update commit flow and changelog generation to use smartfs for reading/writing files and to await version/branch detection where necessary.
|
||||
- Expose a SmartFs instance via plugins and adjust all mod.* plugin files to import/use smartfs where required.
|
||||
- Expose a SmartFs instance via plugins and adjust all mod.\* plugin files to import/use smartfs where required.
|
||||
- Breaking change: Public and internal APIs that previously used synchronous smartfile APIs are now asynchronous. Consumers and scripts must await these functions and use the new smartfs API.
|
||||
|
||||
## 2025-11-17 - 1.21.5 - fix(tsconfig)
|
||||
|
||||
Remove emitDecoratorMetadata from tsconfig template
|
||||
|
||||
- Removed the "emitDecoratorMetadata" compiler option from assets/templates/tsconfig_update/tsconfig.json
|
||||
- This updates the tsconfig template to avoid emitting decorator metadata when targeting ES2022
|
||||
|
||||
## 2025-11-17 - 1.21.4 - fix(tsconfig template)
|
||||
|
||||
Remove experimentalDecorators and useDefineForClassFields from tsconfig template
|
||||
|
||||
- Removed experimentalDecorators option from assets/templates/tsconfig_update/tsconfig.json
|
||||
- Removed useDefineForClassFields option from assets/templates/tsconfig_update/tsconfig.json
|
||||
|
||||
## 2025-11-17 - 1.21.3 - fix(assets/templates/multienv)
|
||||
|
||||
Remove unused Bun configuration template (assets/templates/multienv/bunfig.toml)
|
||||
|
||||
- Deleted assets/templates/multienv/bunfig.toml which previously provided Bun TypeScript decorator configuration
|
||||
@@ -85,17 +201,20 @@ Remove unused Bun configuration template (assets/templates/multienv/bunfig.toml)
|
||||
- No functional code changes; removes an unused asset file
|
||||
|
||||
## 2025-11-17 - 1.21.2 - fix(templates/multienv)
|
||||
|
||||
Disable useDefineForClassFields in multienv TypeScript configs to ensure decorator compatibility
|
||||
|
||||
- Set useDefineForClassFields = false in assets/templates/multienv/bunfig.toml to keep Bun's transpiler compatible with decorator usage
|
||||
- Set "useDefineForClassFields": false in assets/templates/multienv/deno.json to ensure Deno/TypeScript compiler emits class fields compatible with decorators
|
||||
|
||||
## 2025-11-17 - 1.21.1 - fix(templates.multienv)
|
||||
|
||||
Enable checkJs in multienv Deno template to enable JS type checking
|
||||
|
||||
- Added "checkJs": true to compilerOptions in assets/templates/multienv/deno.json to enable JavaScript type checking for the Deno multienv template
|
||||
|
||||
## 2025-11-17 - 1.21.0 - feat(multienv)
|
||||
|
||||
Add multi-env templates enabling TypeScript decorators for Bun and Deno; rename npmextra config key to szci
|
||||
|
||||
- Added assets/templates/multienv/bunfig.toml to enable Bun TypeScript transpiler experimentalDecorators
|
||||
@@ -103,6 +222,7 @@ Add multi-env templates enabling TypeScript decorators for Bun and Deno; rename
|
||||
- Updated npmextra.json: renamed top-level config key from "npmci" to "szci" (keeps npmGlobalTools, npmAccessLevel and npmRegistryUrl unchanged)
|
||||
|
||||
## 2025-11-06 - 1.20.0 - feat(commit)
|
||||
|
||||
Add non-interactive --yes (-y) flag to commit command to auto-accept AI recommendations and optionally push with -p
|
||||
|
||||
- Add -y / --yes flag to gitzone commit to auto-accept AI-generated commit recommendations without interactive prompts
|
||||
@@ -112,6 +232,7 @@ Add non-interactive --yes (-y) flag to commit command to auto-accept AI recommen
|
||||
- Updated CLI usage and documentation (readme.hints.md) to document the new flags
|
||||
|
||||
## 2025-11-05 - 1.19.9 - fix(mod_commit)
|
||||
|
||||
Refactor version bumping to a unified implementation for npm and Deno; remove npm-exec based helpers and add file-based version readers/updaters to avoid npm warning pollution
|
||||
|
||||
- Removed legacy npm/deno-specific helpers (bumpNpmVersion, syncVersionToDenoJson, bumpDenoVersion) that relied on executing npm and caused warning pollution
|
||||
@@ -122,39 +243,46 @@ Refactor version bumping to a unified implementation for npm and Deno; remove np
|
||||
- Benefits: no npm warning pollution in deno.json, simpler git history, consistent behavior across project types
|
||||
|
||||
## 2025-11-04 - 1.19.8 - fix(package.json)
|
||||
|
||||
Bump @git.zone/tsdoc dependency to ^1.9.2
|
||||
|
||||
- Updated dependency @git.zone/tsdoc from ^1.9.1 to ^1.9.2 in package.json
|
||||
|
||||
## 2025-11-04 - 1.19.7 - fix(dependencies)
|
||||
|
||||
Bump @git.zone/tsdoc to ^1.9.1
|
||||
|
||||
- Updated package.json dependency @git.zone/tsdoc from ^1.9.0 to ^1.9.1
|
||||
|
||||
## 2025-11-04 - 1.19.6 - fix(cli)
|
||||
|
||||
Bump @git.zone/tsdoc dependency to ^1.9.0
|
||||
|
||||
- Updated dependency @git.zone/tsdoc from ^1.8.3 to ^1.9.0 in package.json
|
||||
|
||||
## 2025-11-04 - 1.19.5 - fix(cli)
|
||||
|
||||
Bump @git.zone/tsdoc to ^1.8.3 and add local .claude settings for allowed permissions
|
||||
|
||||
- Updated dependency @git.zone/tsdoc from ^1.8.2 to ^1.8.3
|
||||
- Added .claude/settings.local.json to declare allowed permissions for local tooling (Bash commands, Docker, npm, WebFetch and MCP actions)
|
||||
|
||||
## 2025-11-03 - 1.19.3 - fix(tsdoc)
|
||||
|
||||
Bump @git.zone/tsdoc to ^1.8.0 and add .claude local settings
|
||||
|
||||
- Upgrade dependency @git.zone/tsdoc from ^1.6.1 to ^1.8.0 in package.json
|
||||
- Add .claude/settings.local.json for local assistant permissions/configuration
|
||||
|
||||
## 2025-11-03 - 1.19.2 - fix(tsdoc)
|
||||
|
||||
Bump @git.zone/tsdoc to ^1.6.1 and add .claude/settings.local.json
|
||||
|
||||
- Update dependency @git.zone/tsdoc from ^1.6.0 to ^1.6.1
|
||||
- Add .claude/settings.local.json to include local Claude settings/permissions
|
||||
|
||||
## 2025-11-02 - 1.19.1 - fix(dependencies)
|
||||
|
||||
Bump dependencies and add local Claude settings
|
||||
|
||||
- Bump devDependencies: @git.zone/tsbuild -> ^2.7.1, @git.zone/tsrun -> ^1.6.2, @git.zone/tstest -> ^2.7.0
|
||||
@@ -162,6 +290,7 @@ Bump dependencies and add local Claude settings
|
||||
- Add .claude/settings.local.json (local project permissions/settings file)
|
||||
|
||||
## 2025-10-23 - 1.19.0 - feat(mod_commit)
|
||||
|
||||
Add CLI UI helpers and improve commit workflow with progress, recommendations and summary
|
||||
|
||||
- Introduce ts/mod_commit/mod.ui.ts: reusable CLI UI helpers (pretty headers, sections, AI recommendation box, step printer, commit summary and helpers for consistent messaging).
|
||||
@@ -170,6 +299,7 @@ Add CLI UI helpers and improve commit workflow with progress, recommendations an
|
||||
- Add .claude/settings.local.json: local permissions configuration for development tooling.
|
||||
|
||||
## 2025-10-23 - 1.18.9 - fix(mod_commit)
|
||||
|
||||
Stage and commit deno.json when bumping/syncing versions and create/update git tags
|
||||
|
||||
- bumpDenoVersion now creates a Smartshell instance and runs git add deno.json, git commit -m "v<newVersion>", and git tag v<newVersion> to persist the version bump
|
||||
@@ -177,6 +307,7 @@ Stage and commit deno.json when bumping/syncing versions and create/update git t
|
||||
- Added informative logger messages after creating commits and tags
|
||||
|
||||
## 2025-10-23 - 1.18.8 - fix(mod_commit)
|
||||
|
||||
Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno
|
||||
|
||||
- Add mod_commit/mod.helpers.ts with utilities: detectCurrentBranch(), detectProjectType(), bumpProjectVersion(), bumpDenoVersion(), bumpNpmVersion(), syncVersionToDenoJson(), and calculateNewVersion()
|
||||
@@ -186,12 +317,14 @@ Improve commit workflow: detect project type and current branch; add robust vers
|
||||
- Add local Claude settings file (.claude/settings.local.json) (editor/CI config) — no code behavior change but included in diff
|
||||
|
||||
## 2025-09-07 - 1.18.7 - fix(claude)
|
||||
|
||||
Add .claude local settings to whitelist dev tool permissions
|
||||
|
||||
- Add .claude/settings.local.json to configure allowed permissions for local AI/tooling helpers (Bash commands, WebFetch, and mcp_serena actions).
|
||||
- Disable enableAllProjectMcpServers (set to false) to limit automatic project MCP server usage.
|
||||
|
||||
## 2025-09-07 - 1.18.6 - fix(deps)
|
||||
|
||||
Bump dependency versions and add local Claude settings
|
||||
|
||||
- Updated devDependencies: @git.zone/tsbuild ^2.6.4 → ^2.6.8, @git.zone/tstest ^2.3.4 → ^2.3.6, @push.rocks/smartfile ^11.2.5 → ^11.2.7
|
||||
@@ -199,6 +332,7 @@ Bump dependency versions and add local Claude settings
|
||||
- Added .claude/settings.local.json to configure local Claude permissions/settings
|
||||
|
||||
## 2025-08-17 - 1.18.5 - fix(dependencies)
|
||||
|
||||
Bump smartshell and smartscaf versions; add .claude local settings
|
||||
|
||||
- Update @push.rocks/smartshell from ^3.2.4 to ^3.3.0 in package.json
|
||||
@@ -206,6 +340,7 @@ Bump smartshell and smartscaf versions; add .claude local settings
|
||||
- Add .claude/settings.local.json for local assistant permissions/configuration
|
||||
|
||||
## 2025-08-17 - 1.18.4 - fix(cli)
|
||||
|
||||
Update dependencies, add local Claude settings, and update gitignore template
|
||||
|
||||
- Bump several dependencies: @git.zone/tsbuild -> ^2.6.4, @git.zone/tspublish -> ^1.10.1, @git.zone/tstest -> ^2.3.4, @push.rocks/smartfile -> ^11.2.5, @push.rocks/npmextra -> ^5.3.3, @push.rocks/smartchok -> ^1.1.1, @push.rocks/smartlog -> ^3.1.8, @push.rocks/smartpath -> ^6.0.0, prettier -> ^3.6.2
|
||||
@@ -214,6 +349,7 @@ Update dependencies, add local Claude settings, and update gitignore template
|
||||
- Add pnpm onlyBuiltDependencies entries: esbuild and mongodb-memory-server
|
||||
|
||||
## 2025-08-16 - 1.18.3 - fix(services)
|
||||
|
||||
Simplify S3 endpoint handling in ServiceConfiguration to store host only
|
||||
|
||||
- S3_ENDPOINT now stores the raw host (e.g. 'localhost') instead of a full URL with protocol and port.
|
||||
@@ -222,6 +358,7 @@ Simplify S3 endpoint handling in ServiceConfiguration to store host only
|
||||
- Consumers that previously relied on S3_ENDPOINT containing protocol and port should now construct the full endpoint URL using S3_USESSL, S3_HOST and S3_PORT.
|
||||
|
||||
## 2025-08-16 - 1.18.1 - fix(services)
|
||||
|
||||
Improve services and commit flow: stop AiDoc, use silent docker inspect, sync ports with logging, fix config loading, and bump deps
|
||||
|
||||
- Ensure AiDoc is stopped after building commit recommendation to avoid resource leaks
|
||||
@@ -233,6 +370,7 @@ Improve services and commit flow: stop AiDoc, use silent docker inspect, sync po
|
||||
- Add local Claude settings file (.claude/settings.local.json) with development permissions
|
||||
|
||||
## 2025-08-16 - 1.18.0 - feat(services)
|
||||
|
||||
Add Docker port mapping sync and reconfigure workflow for local services
|
||||
|
||||
- Add getPortMappings to DockerContainer to extract port bindings from docker inspect output
|
||||
@@ -245,6 +383,7 @@ Add Docker port mapping sync and reconfigure workflow for local services
|
||||
- Add .claude/settings.local.json (local permissions config) to repository
|
||||
|
||||
## 2025-08-15 - 1.17.5 - fix(services)
|
||||
|
||||
Update S3 credentials naming and add S3_ENDPOINT/S3_USESSL support for improved MinIO integration
|
||||
|
||||
- Replaced S3_USER/S3_PASS with S3_ACCESSKEY/S3_SECRETKEY in ServiceConfiguration
|
||||
@@ -254,6 +393,7 @@ Update S3 credentials naming and add S3_ENDPOINT/S3_USESSL support for improved
|
||||
- Added .claude/settings.local.json for local permission settings
|
||||
|
||||
## 2025-08-15 - 1.17.4 - fix(services)
|
||||
|
||||
Update S3 credentials naming and add S3_ENDPOINT/S3_USESSL support for improved MinIO integration
|
||||
|
||||
- Replaced S3_USER/S3_PASS with S3_ACCESSKEY/S3_SECRETKEY in ServiceConfiguration
|
||||
@@ -262,12 +402,14 @@ Update S3 credentials naming and add S3_ENDPOINT/S3_USESSL support for improved
|
||||
- Updated ServiceManager to use new credential names in container setup and logging
|
||||
|
||||
## 2025-08-15 - 1.17.3 - fix(serviceconfig)
|
||||
|
||||
Update service configuration to include dynamic MongoDB connection string and add local permissions settings
|
||||
|
||||
- Added .claude/settings.local.json for local permissions configuration
|
||||
- Updated ServiceConfiguration to compute and update MONGODB_URL based on current config values
|
||||
|
||||
## 2025-08-15 - 1.17.2 - fix(ci-test-services)
|
||||
|
||||
Update CI/CD configurations, test settings, and Docker service for MongoDB.
|
||||
|
||||
- Add .claude/settings.local.json with updated permission settings
|
||||
@@ -276,6 +418,7 @@ Update CI/CD configurations, test settings, and Docker service for MongoDB.
|
||||
- Fix MongoDB Docker container command by adding '--bind_ip_all' for proper network binding
|
||||
|
||||
## 2025-08-15 - 1.17.1 - fix(services)
|
||||
|
||||
Improve services module logging and enhance MongoDB Compass integration
|
||||
|
||||
- Refactored services module to use centralized logger from gitzone.logging.ts
|
||||
@@ -284,6 +427,7 @@ Improve services module logging and enhance MongoDB Compass integration
|
||||
- Consistent logging across all service commands
|
||||
|
||||
## 2025-08-14 - 1.17.0 - feat(services)
|
||||
|
||||
Add comprehensive development services management for MongoDB and MinIO containers
|
||||
|
||||
- Implemented `gitzone services` command for managing local development services
|
||||
@@ -297,6 +441,7 @@ Add comprehensive development services management for MongoDB and MinIO containe
|
||||
- Interactive confirmations for destructive operations
|
||||
|
||||
## 2025-08-08 - 1.16.10 - fix(format)
|
||||
|
||||
Improve concurrency control in caching and rollback modules, refine gitignore custom section handling, and enhance Prettier file processing.
|
||||
|
||||
- Added mutex locking in ChangeCache and RollbackManager to prevent race conditions during manifest updates
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
"npmAccessLevel": "private",
|
||||
"npmRegistryUrl": "verdaccio.lossless.one"
|
||||
},
|
||||
"gitzone": {
|
||||
"tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
},
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
@@ -31,15 +34,11 @@
|
||||
"Docker",
|
||||
"CI/CD"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
},
|
||||
"@git.zone/cli": {
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital"
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@git.zone/cli",
|
||||
"private": false,
|
||||
"version": "2.5.0",
|
||||
"version": "2.12.2",
|
||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||
"main": "dist_ts/index.ts",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
@@ -67,7 +67,7 @@
|
||||
"@types/node": "^25.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@git.zone/tsdoc": "^1.10.2",
|
||||
"@git.zone/tsdoc": "^1.11.3",
|
||||
"@git.zone/tspublish": "^1.10.3",
|
||||
"@push.rocks/commitinfo": "^1.0.12",
|
||||
"@push.rocks/early": "^4.0.4",
|
||||
@@ -76,7 +76,7 @@
|
||||
"@push.rocks/npmextra": "^5.3.3",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@push.rocks/smartcli": "^4.0.19",
|
||||
"@push.rocks/smartdiff": "^1.0.3",
|
||||
"@push.rocks/smartdiff": "^1.1.0",
|
||||
"@push.rocks/smartfile": "^13.1.2",
|
||||
"@push.rocks/smartfs": "^1.2.0",
|
||||
"@push.rocks/smartgulp": "^3.0.4",
|
||||
|
||||
107
pnpm-lock.yaml
generated
107
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@git.zone/tsdoc':
|
||||
specifier: ^1.10.2
|
||||
version: 1.10.2(ws@8.18.3)(zod@3.25.76)
|
||||
specifier: ^1.11.3
|
||||
version: 1.11.3(ws@8.18.3)(zod@3.25.76)
|
||||
'@git.zone/tspublish':
|
||||
specifier: ^1.10.3
|
||||
version: 1.10.3
|
||||
@@ -36,8 +36,8 @@ importers:
|
||||
specifier: ^4.0.19
|
||||
version: 4.0.19
|
||||
'@push.rocks/smartdiff':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
'@push.rocks/smartfile':
|
||||
specifier: ^13.1.2
|
||||
version: 13.1.2
|
||||
@@ -520,8 +520,8 @@ packages:
|
||||
resolution: {integrity: sha512-YD1qMYA/4eOuF57V0ccR+xo6ww1+QOYFA2K5gBPFBDNh9VdfvWxxDhOUybja8lT9PVMoli8PHG5WA5tKJkdXIQ==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tsdoc@1.10.2':
|
||||
resolution: {integrity: sha512-r4pKv74CH0KtzRvGdLioJd3DznSKmr8ZVE43QPFfGSNftH5P2eLAe5lc5nK8gCWb8mgEkb8WNfqtTL3Lkg+XyQ==}
|
||||
'@git.zone/tsdoc@1.11.3':
|
||||
resolution: {integrity: sha512-U6X9laKv9CTZiqtQpqVMZ2x3qKH1ucey3y16T5UQ70j7wza2GV9rwdkTIHgpYrWFBMSoh909T+ELH8qWbRqimw==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tspublish@1.10.3':
|
||||
@@ -999,12 +999,18 @@ packages:
|
||||
'@push.rocks/qenv@6.1.3':
|
||||
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
|
||||
|
||||
'@push.rocks/smartagent@1.2.5':
|
||||
resolution: {integrity: sha512-qV7zyHbp5p5ySg16uipjIdYzKM85fn5/l97pKlZz9awRZhOcvYblmypQRKHlMc+O2mVevxLY4Q/6pzYwI8UXvw==}
|
||||
|
||||
'@push.rocks/smartai@0.8.0':
|
||||
resolution: {integrity: sha512-guzi28meUDc3mydC8kpoA+4pzExRQqygXYFDD4qQSWPpIRHQ7qhpeNqJzrrGezT1yOH5Gb9taPEGwT56hI+nwQ==}
|
||||
|
||||
'@push.rocks/smartarchive@4.2.4':
|
||||
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
|
||||
|
||||
'@push.rocks/smartarchive@5.0.1':
|
||||
resolution: {integrity: sha512-x4bie9IIdL9BZqBZLc8Pemp8xZOJGa6mXSVgKJRL4/Rw+E5N4rVHjQOYGRV75nC2mAMJh9GIbixuxLnWjj77ag==}
|
||||
|
||||
'@push.rocks/smartarray@1.1.0':
|
||||
resolution: {integrity: sha512-b5YgBmUdglOJH8zeUf2ZWdPCoqySgwvkycRi2BhA9zVZHkpASh39Ej0q0fxFJetlUVyYqGfVoMVjbVrLFfFV7g==}
|
||||
|
||||
@@ -1042,8 +1048,11 @@ packages:
|
||||
'@push.rocks/smartdelay@3.0.5':
|
||||
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
||||
|
||||
'@push.rocks/smartdiff@1.0.3':
|
||||
resolution: {integrity: sha512-cXUKj0KJBxnrZDN1Ztc2WiFRJM3vOTdQUdBfe6ar5NlKuXytSRMJqVL8IUbtWfMCSOx6HgWAUT7W68+/X2TG8w==}
|
||||
'@push.rocks/smartdeno@1.2.0':
|
||||
resolution: {integrity: sha512-6S1plCaMUVOZiRSflfoz9Fqk9phACCuKmc7Z6SfTvfl+p9VcPUmewKgaa/0QiLOpiI6ksfxdfmkS5Rw5HpYeIA==}
|
||||
|
||||
'@push.rocks/smartdiff@1.1.0':
|
||||
resolution: {integrity: sha512-AAz/unmko0C+g+60odOoK32PE3Ci3YLoB+zfg1LGLyVRCthcdzjqa1C2Km0MfG7IyJQKPdj8J5HPubtpm3ZeaQ==}
|
||||
|
||||
'@push.rocks/smartdns@7.6.1':
|
||||
resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==}
|
||||
@@ -2536,9 +2545,6 @@ packages:
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-diff@1.3.0:
|
||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
||||
|
||||
fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
|
||||
@@ -2707,9 +2713,6 @@ packages:
|
||||
resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
gpt-tokenizer@3.4.0:
|
||||
resolution: {integrity: sha512-wxFLnhIXTDjYebd9A9pGl3e31ZpSypbpIJSOswbgop5jLte/AsZVDvjlbEuVFlsqZixVKqbcoNmRlFDf6pz/UQ==}
|
||||
|
||||
graceful-fs@4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
|
||||
@@ -2908,8 +2911,8 @@ packages:
|
||||
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
isomorphic-git@1.36.0:
|
||||
resolution: {integrity: sha512-22tU165ptowHYoDEwYJy5EKRzpHiuLMliaR01fH9ZwaUj1z/IqE++tGpjw/pD6eCWoxiOp6TPWX434aJ9zA4Lg==}
|
||||
isomorphic-git@1.36.1:
|
||||
resolution: {integrity: sha512-fC8SRT8MwoaXDK8G4z5biPEbqf2WyEJUb2MJ2ftSd39/UIlsnoZxLGux+lae0poLZO4AEcx6aUVOh5bV+P8zFA==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
@@ -5063,12 +5066,13 @@ snapshots:
|
||||
- '@swc/helpers'
|
||||
- supports-color
|
||||
|
||||
'@git.zone/tsdoc@1.10.2(ws@8.18.3)(zod@3.25.76)':
|
||||
'@git.zone/tsdoc@1.11.3(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@git.zone/tspublish': 1.10.3
|
||||
'@push.rocks/early': 4.0.4
|
||||
'@push.rocks/npmextra': 5.3.3
|
||||
'@push.rocks/qenv': 6.1.3
|
||||
'@push.rocks/smartagent': 1.2.5(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
|
||||
'@push.rocks/smartai': 0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
|
||||
'@push.rocks/smartcli': 4.0.19
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -5081,7 +5085,6 @@ snapshots:
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
gpt-tokenizer: 3.4.0
|
||||
typedoc: 0.28.15(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -5880,6 +5883,30 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.1.10
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
|
||||
'@push.rocks/smartagent@1.2.5(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@push.rocks/smartai': 0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
|
||||
'@push.rocks/smartbrowser': 2.0.8(typescript@5.9.3)
|
||||
'@push.rocks/smartdeno': 1.2.0
|
||||
'@push.rocks/smartfs': 1.2.0
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
minimatch: 10.1.1
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- aws-crt
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
- bufferutil
|
||||
- react
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
- vue
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@push.rocks/smartai@0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.65.0(zod@3.25.76)
|
||||
@@ -5926,6 +5953,26 @@ snapshots:
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
|
||||
'@push.rocks/smartarchive@5.0.1':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrequest': 4.4.2
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartstream': 3.2.5
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
'@push.rocks/smarturl': 3.1.0
|
||||
'@types/tar-stream': 3.1.4
|
||||
fflate: 0.8.2
|
||||
file-type: 21.1.1
|
||||
tar-stream: 3.1.7
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
|
||||
'@push.rocks/smartarray@1.1.0': {}
|
||||
|
||||
'@push.rocks/smartbrowser@2.0.8(typescript@5.9.3)':
|
||||
@@ -6049,9 +6096,21 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
|
||||
'@push.rocks/smartdiff@1.0.3':
|
||||
'@push.rocks/smartdeno@1.2.0':
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
'@push.rocks/smartarchive': 5.0.1
|
||||
'@push.rocks/smartfs': 1.2.0
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
'@push.rocks/smartunique': 3.0.9
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
|
||||
'@push.rocks/smartdiff@1.1.0':
|
||||
dependencies:
|
||||
diff: 8.0.2
|
||||
|
||||
'@push.rocks/smartdns@7.6.1':
|
||||
dependencies:
|
||||
@@ -6180,7 +6239,7 @@ snapshots:
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@types/diff': 8.0.0
|
||||
diff: 8.0.2
|
||||
isomorphic-git: 1.36.0
|
||||
isomorphic-git: 1.36.1
|
||||
minimatch: 10.1.1
|
||||
|
||||
'@push.rocks/smartguard@3.1.0':
|
||||
@@ -8145,8 +8204,6 @@ snapshots:
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-diff@1.3.0: {}
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
@@ -8364,8 +8421,6 @@ snapshots:
|
||||
p-cancelable: 3.0.0
|
||||
responselike: 3.0.0
|
||||
|
||||
gpt-tokenizer@3.4.0: {}
|
||||
|
||||
graceful-fs@4.2.10: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
@@ -8578,7 +8633,7 @@ snapshots:
|
||||
|
||||
isexe@3.1.1: {}
|
||||
|
||||
isomorphic-git@1.36.0:
|
||||
isomorphic-git@1.36.1:
|
||||
dependencies:
|
||||
async-lock: 1.4.1
|
||||
clean-git-ref: 2.0.1
|
||||
|
||||
@@ -96,6 +96,7 @@ The format module is responsible for project standardization:
|
||||
The commit module's version bumping has been refactored to eliminate npm command dependencies:
|
||||
|
||||
**Changes:**
|
||||
|
||||
- Removed `bumpNpmVersion()` - was causing npm warnings to pollute deno.json
|
||||
- Removed `syncVersionToDenoJson()` - no longer needed with unified approach
|
||||
- Removed separate `bumpDenoVersion()` - replaced by unified implementation
|
||||
@@ -104,6 +105,7 @@ The commit module's version bumping has been refactored to eliminate npm command
|
||||
- Unified `bumpProjectVersion()` - handles npm/deno/both with single clean code path
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- No npm warning pollution in version fields
|
||||
- Full control over version bumping process
|
||||
- Simpler git history (no amending, no force-tagging)
|
||||
@@ -115,11 +117,13 @@ The commit module's version bumping has been refactored to eliminate npm command
|
||||
The commit module now supports `-y/--yes` flag for non-interactive commits:
|
||||
|
||||
**Usage:**
|
||||
|
||||
- `gitzone commit -y` - Auto-accepts AI recommendations without prompts
|
||||
- `gitzone commit -yp` - Auto-accepts and pushes to origin
|
||||
- Separate `-p/--push` flag controls push behavior
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Creates AnswerBucket programmatically when `-y` flag detected
|
||||
- Preserves all UI output for transparency
|
||||
- Fully backward compatible with interactive mode
|
||||
@@ -248,10 +252,12 @@ gitzone format --clean-backups
|
||||
The project has been fully migrated from @push.rocks/smartfile v11 to v13, which introduced a major breaking change where filesystem operations were split into two separate packages:
|
||||
|
||||
**Packages:**
|
||||
|
||||
- `@push.rocks/smartfile` v13.0.1 - File representation classes (SmartFile, StreamFile, VirtualDirectory)
|
||||
- `@push.rocks/smartfs` v1.1.0 - Filesystem operations (read, write, exists, stat, etc.)
|
||||
|
||||
**Key API Changes:**
|
||||
|
||||
1. **File Reading**:
|
||||
- Old: `plugins.smartfile.fs.toStringSync(path)` or `plugins.smartfile.fs.toObjectSync(path)`
|
||||
- New: `await plugins.smartfs.file(path).encoding('utf8').read()` + JSON.parse if needed
|
||||
@@ -290,13 +296,15 @@ The project has been fully migrated from @push.rocks/smartfile v11 to v13, which
|
||||
All sync methods must become async. Functions that were previously synchronous (like `getProjectName()`) now return `Promise<T>` and must be awaited.
|
||||
|
||||
**Affected Modules:**
|
||||
- ts/mod_format/* (largest area - 15+ files)
|
||||
- ts/mod_commit/* (version bumping)
|
||||
- ts/mod_services/* (configuration management)
|
||||
- ts/mod_meta/* (meta repository management)
|
||||
- ts/mod_standard/* (template listing)
|
||||
- ts/mod_template/* (template operations)
|
||||
|
||||
- ts/mod_format/\* (largest area - 15+ files)
|
||||
- ts/mod_commit/\* (version bumping)
|
||||
- ts/mod_services/\* (configuration management)
|
||||
- ts/mod_meta/\* (meta repository management)
|
||||
- ts/mod_standard/\* (template listing)
|
||||
- ts/mod_template/\* (template operations)
|
||||
|
||||
**Previous API Changes:**
|
||||
|
||||
- smartnpm requires instance creation: `new NpmRegistry()`
|
||||
- Type imports use `import type` for proper verbatim module syntax
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# GitZone Services Command Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Implement the `gitzone services` command to manage MongoDB and MinIO containers for development projects.
|
||||
|
||||
## Tasks
|
||||
|
||||
### Module Structure Setup
|
||||
|
||||
- [x] Create `ts/mod_services/` directory
|
||||
- [x] Create `mod.plugins.ts` with required imports
|
||||
- [x] Create `helpers.ts` with utility functions
|
||||
@@ -15,6 +17,7 @@ Implement the `gitzone services` command to manage MongoDB and MinIO containers
|
||||
- [x] Create `index.ts` with main command logic
|
||||
|
||||
### Core Functionality
|
||||
|
||||
- [x] Implement ServiceConfiguration class
|
||||
- [x] Load/create `.nogit/env.json` configuration
|
||||
- [x] Generate random available ports (20000-30000 range)
|
||||
@@ -37,6 +40,7 @@ Implement the `gitzone services` command to manage MongoDB and MinIO containers
|
||||
- [x] Generate MongoDB Compass connection strings
|
||||
|
||||
### Commands Implementation
|
||||
|
||||
- [x] `start` command - Start services (mongo|s3|all)
|
||||
- [x] `stop` command - Stop services (mongo|s3|all)
|
||||
- [x] `restart` command - Restart services (mongo|s3|all)
|
||||
@@ -48,12 +52,14 @@ Implement the `gitzone services` command to manage MongoDB and MinIO containers
|
||||
- [x] `clean` command - Remove containers and data
|
||||
|
||||
### Integration
|
||||
|
||||
- [x] Add `@push.rocks/smartshell` to main plugins.ts
|
||||
- [x] Add `@push.rocks/smartnetwork` to main plugins.ts
|
||||
- [x] Add `@push.rocks/smartinteraction` to main plugins.ts
|
||||
- [x] Register services command in `gitzone.cli.ts`
|
||||
|
||||
### Features
|
||||
|
||||
- [x] Auto-configuration with smart defaults
|
||||
- [x] Random port assignment to avoid conflicts
|
||||
- [x] Project isolation with unique container names
|
||||
@@ -65,6 +71,7 @@ Implement the `gitzone services` command to manage MongoDB and MinIO containers
|
||||
- [x] MongoDB Compass connection string with network IP
|
||||
|
||||
### Testing
|
||||
|
||||
- [ ] Test service start/stop operations
|
||||
- [ ] Test configuration creation and updates
|
||||
- [ ] Test port collision handling
|
||||
@@ -73,6 +80,7 @@ Implement the `gitzone services` command to manage MongoDB and MinIO containers
|
||||
- [ ] Test all command variations
|
||||
|
||||
## Configuration Format
|
||||
|
||||
```json
|
||||
{
|
||||
"PROJECT_NAME": "derived-from-package-name",
|
||||
@@ -91,6 +99,7 @@ Implement the `gitzone services` command to manage MongoDB and MinIO containers
|
||||
```
|
||||
|
||||
## Command Examples
|
||||
|
||||
```bash
|
||||
gitzone services start # Start all services
|
||||
gitzone services start mongo # Start only MongoDB
|
||||
@@ -104,10 +113,12 @@ gitzone services clean # Remove containers and data
|
||||
```
|
||||
|
||||
## Progress Notes
|
||||
|
||||
Implementation started: 2025-08-14
|
||||
Implementation completed: 2025-08-14
|
||||
|
||||
## Summary
|
||||
|
||||
Successfully implemented the `gitzone services` command in TypeScript, providing a complete replacement for the `services.sh` shell script. The implementation includes:
|
||||
|
||||
1. **Complete Docker service management** for MongoDB and MinIO containers
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/cli',
|
||||
version: '2.5.0',
|
||||
version: '2.12.2',
|
||||
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import type { TGitzoneProjectType } from './classes.gitzoneconfig.js';
|
||||
* the Project class is a tool to work with a gitzone project
|
||||
*/
|
||||
export class Project {
|
||||
public static async fromCwd() {
|
||||
public static async fromCwd(options: { requireProjectType?: boolean } = {}) {
|
||||
const gitzoneConfig = await GitzoneConfig.fromCwd();
|
||||
const project = new Project(gitzoneConfig);
|
||||
if (!project.gitzoneConfig.data.projectType) {
|
||||
const requireProjectType = options.requireProjectType ?? true;
|
||||
if (requireProjectType && !project.gitzoneConfig.data.projectType) {
|
||||
throw new Error('Please define a project type');
|
||||
}
|
||||
return project;
|
||||
|
||||
@@ -80,7 +80,9 @@ export let run = async () => {
|
||||
}
|
||||
|
||||
// Handle format with options
|
||||
// Default is dry-mode, use --write/-w to apply changes
|
||||
await modFormat.run({
|
||||
write: argvArg.write || argvArg.w,
|
||||
dryRun: argvArg['dry-run'],
|
||||
yes: argvArg.yes,
|
||||
planOnly: argvArg['plan-only'],
|
||||
@@ -90,6 +92,7 @@ export let run = async () => {
|
||||
interactive: argvArg.interactive !== false,
|
||||
parallel: argvArg.parallel !== false,
|
||||
verbose: argvArg.verbose,
|
||||
diff: argvArg.diff,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,9 +8,20 @@ import * as ui from './mod.ui.js';
|
||||
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
||||
|
||||
export const run = async (argvArg: any) => {
|
||||
// Check if release flag is set and validate registries early
|
||||
// Read commit config from npmextra.json
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||
const gitzoneConfig = npmextraConfig.dataFor<{
|
||||
commit?: {
|
||||
alwaysTest?: boolean;
|
||||
alwaysBuild?: boolean;
|
||||
};
|
||||
}>('@git.zone/cli', {});
|
||||
const commitConfig = gitzoneConfig.commit || {};
|
||||
|
||||
// Check flags and merge with config options
|
||||
const wantsRelease = !!(argvArg.r || argvArg.release);
|
||||
const wantsBuild = !!(argvArg.b || argvArg.build);
|
||||
const wantsTest = !!(argvArg.t || argvArg.test || commitConfig.alwaysTest);
|
||||
const wantsBuild = !!(argvArg.b || argvArg.build || commitConfig.alwaysBuild);
|
||||
let releaseConfig: ReleaseConfig | null = null;
|
||||
|
||||
if (wantsRelease) {
|
||||
@@ -24,11 +35,37 @@ export const run = async (argvArg: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Print execution plan at the start
|
||||
ui.printExecutionPlan({
|
||||
autoAccept: !!(argvArg.y || argvArg.yes),
|
||||
push: !!(argvArg.p || argvArg.push),
|
||||
test: wantsTest,
|
||||
build: wantsBuild,
|
||||
release: wantsRelease,
|
||||
format: !!argvArg.format,
|
||||
registries: releaseConfig?.getRegistries(),
|
||||
});
|
||||
|
||||
if (argvArg.format) {
|
||||
const formatMod = await import('../mod_format/index.js');
|
||||
await formatMod.run();
|
||||
}
|
||||
|
||||
// Run tests early to fail fast before analysis
|
||||
if (wantsTest) {
|
||||
ui.printHeader('🧪 Running tests...');
|
||||
const smartshellForTest = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
sourceFilePaths: [],
|
||||
});
|
||||
const testResult = await smartshellForTest.exec('pnpm test');
|
||||
if (testResult.exitCode !== 0) {
|
||||
logger.log('error', 'Tests failed. Aborting commit.');
|
||||
process.exit(1);
|
||||
}
|
||||
logger.log('success', 'All tests passed.');
|
||||
}
|
||||
|
||||
ui.printHeader('🔍 Analyzing repository changes...');
|
||||
|
||||
const aidoc = new plugins.tsdoc.AiDoc();
|
||||
@@ -151,6 +188,7 @@ export const run = async (argvArg: any) => {
|
||||
}
|
||||
|
||||
// Determine total steps based on options
|
||||
// Note: test runs early (like format) so not counted in numbered steps
|
||||
const willPush = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true');
|
||||
const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
|
||||
let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version
|
||||
|
||||
@@ -18,6 +18,16 @@ interface ICommitSummary {
|
||||
releasedRegistries?: string[];
|
||||
}
|
||||
|
||||
interface IExecutionPlanOptions {
|
||||
autoAccept: boolean;
|
||||
push: boolean;
|
||||
test: boolean;
|
||||
build: boolean;
|
||||
release: boolean;
|
||||
format: boolean;
|
||||
registries?: string[];
|
||||
}
|
||||
|
||||
interface IRecommendation {
|
||||
recommendedNextVersion: string;
|
||||
recommendedNextVersionLevel: string;
|
||||
@@ -41,6 +51,58 @@ export function printHeader(title: string): void {
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print execution plan at the start of commit
|
||||
*/
|
||||
export function printExecutionPlan(options: IExecutionPlanOptions): void {
|
||||
console.log('');
|
||||
console.log('╭───────────────────────────────────────────────────────────────╮');
|
||||
console.log('│ gitzone commit - Execution Plan │');
|
||||
console.log('╰───────────────────────────────────────────────────────────────╯');
|
||||
console.log('');
|
||||
|
||||
// Show active options
|
||||
console.log(' Options:');
|
||||
console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`);
|
||||
console.log(` Push to remote ${options.push ? '✓ enabled (-p)' : '○ disabled'}`);
|
||||
console.log(` Test first ${options.test ? '✓ enabled (-t)' : '○ disabled'}`);
|
||||
console.log(` Build & verify ${options.build ? '✓ enabled (-b)' : '○ disabled'}`);
|
||||
console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`);
|
||||
if (options.format) {
|
||||
console.log(` Format first ✓ enabled (--format)`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Show steps
|
||||
console.log(' Steps:');
|
||||
let stepNum = 1;
|
||||
if (options.format) {
|
||||
console.log(` ${stepNum++}. Format project files`);
|
||||
}
|
||||
if (options.test) {
|
||||
console.log(` ${stepNum++}. Run tests`);
|
||||
}
|
||||
console.log(` ${stepNum++}. Analyze repository changes`);
|
||||
console.log(` ${stepNum++}. Bake commit info into code`);
|
||||
console.log(` ${stepNum++}. Generate changelog.md`);
|
||||
console.log(` ${stepNum++}. Stage and commit files`);
|
||||
console.log(` ${stepNum++}. Bump version`);
|
||||
if (options.build) {
|
||||
console.log(` ${stepNum++}. Run build`);
|
||||
console.log(` ${stepNum++}. Verify clean working tree`);
|
||||
}
|
||||
if (options.push) {
|
||||
console.log(` ${stepNum++}. Push to remote`);
|
||||
}
|
||||
if (options.release) {
|
||||
console.log(` ${stepNum++}. Publish to npm registries`);
|
||||
if (options.registries && options.registries.length > 0) {
|
||||
options.registries.forEach(r => console.log(` → ${r}`));
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a section with a border
|
||||
*/
|
||||
|
||||
104
ts/mod_config/classes.commitconfig.ts
Normal file
104
ts/mod_config/classes.commitconfig.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
|
||||
export interface ICommitConfig {
|
||||
alwaysTest: boolean;
|
||||
alwaysBuild: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages commit configuration stored in npmextra.json
|
||||
* under @git.zone/cli.commit namespace
|
||||
*/
|
||||
export class CommitConfig {
|
||||
private cwd: string;
|
||||
private config: ICommitConfig;
|
||||
|
||||
constructor(cwd: string = process.cwd()) {
|
||||
this.cwd = cwd;
|
||||
this.config = { alwaysTest: false, alwaysBuild: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CommitConfig instance from current working directory
|
||||
*/
|
||||
public static async fromCwd(cwd: string = process.cwd()): Promise<CommitConfig> {
|
||||
const instance = new CommitConfig(cwd);
|
||||
await instance.load();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from npmextra.json
|
||||
*/
|
||||
public async load(): Promise<void> {
|
||||
const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd);
|
||||
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
|
||||
|
||||
this.config = {
|
||||
alwaysTest: gitzoneConfig?.commit?.alwaysTest ?? false,
|
||||
alwaysBuild: gitzoneConfig?.commit?.alwaysBuild ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration to npmextra.json
|
||||
*/
|
||||
public async save(): Promise<void> {
|
||||
const npmextraPath = plugins.path.join(this.cwd, 'npmextra.json');
|
||||
let npmextraData: any = {};
|
||||
|
||||
// Read existing npmextra.json
|
||||
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
||||
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
||||
npmextraData = JSON.parse(content as string);
|
||||
}
|
||||
|
||||
// Ensure @git.zone/cli namespace exists
|
||||
if (!npmextraData['@git.zone/cli']) {
|
||||
npmextraData['@git.zone/cli'] = {};
|
||||
}
|
||||
|
||||
// Ensure commit object exists
|
||||
if (!npmextraData['@git.zone/cli'].commit) {
|
||||
npmextraData['@git.zone/cli'].commit = {};
|
||||
}
|
||||
|
||||
// Update commit settings
|
||||
npmextraData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest;
|
||||
npmextraData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild;
|
||||
|
||||
// Write back to file
|
||||
await plugins.smartfs
|
||||
.file(npmextraPath)
|
||||
.encoding('utf8')
|
||||
.write(JSON.stringify(npmextraData, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alwaysTest setting
|
||||
*/
|
||||
public getAlwaysTest(): boolean {
|
||||
return this.config.alwaysTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alwaysTest setting
|
||||
*/
|
||||
public setAlwaysTest(value: boolean): void {
|
||||
this.config.alwaysTest = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alwaysBuild setting
|
||||
*/
|
||||
public getAlwaysBuild(): boolean {
|
||||
return this.config.alwaysBuild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alwaysBuild setting
|
||||
*/
|
||||
public setAlwaysBuild(value: boolean): void {
|
||||
this.config.alwaysBuild = value;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,32 @@
|
||||
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { ReleaseConfig } from './classes.releaseconfig.js';
|
||||
import { runFormatter } from '../mod_format/index.js';
|
||||
import { CommitConfig } from './classes.commitconfig.js';
|
||||
import { runFormatter, type ICheckResult } from '../mod_format/index.js';
|
||||
|
||||
export { ReleaseConfig };
|
||||
export { ReleaseConfig, CommitConfig };
|
||||
|
||||
/**
|
||||
* Format npmextra.json with diff preview
|
||||
* Shows diff first, asks for confirmation, then applies
|
||||
*/
|
||||
async function formatNpmextraWithDiff(): Promise<void> {
|
||||
// Check for diffs first
|
||||
const checkResult = await runFormatter('npmextra', {
|
||||
checkOnly: true,
|
||||
showDiff: true,
|
||||
}) as ICheckResult | void;
|
||||
|
||||
if (checkResult && checkResult.hasDiff) {
|
||||
const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||
'Apply formatting changes to npmextra.json?',
|
||||
true
|
||||
);
|
||||
if (shouldApply) {
|
||||
await runFormatter('npmextra', { silent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const run = async (argvArg: any) => {
|
||||
const command = argvArg._?.[1];
|
||||
@@ -33,6 +56,12 @@ export const run = async (argvArg: any) => {
|
||||
case 'accessLevel':
|
||||
await handleAccessLevel(value);
|
||||
break;
|
||||
case 'commit':
|
||||
await handleCommit(argvArg._?.[2], argvArg._?.[3]);
|
||||
break;
|
||||
case 'services':
|
||||
await handleServices();
|
||||
break;
|
||||
case 'help':
|
||||
showHelp();
|
||||
break;
|
||||
@@ -48,7 +77,7 @@ export const run = async (argvArg: any) => {
|
||||
async function handleInteractiveMenu(): Promise<void> {
|
||||
console.log('');
|
||||
console.log('╭─────────────────────────────────────────────────────────────╮');
|
||||
console.log('│ gitzone config - Release Configuration │');
|
||||
console.log('│ gitzone config - Project Configuration │');
|
||||
console.log('╰─────────────────────────────────────────────────────────────╯');
|
||||
console.log('');
|
||||
|
||||
@@ -64,6 +93,8 @@ async function handleInteractiveMenu(): Promise<void> {
|
||||
{ name: 'Remove a registry', value: 'remove' },
|
||||
{ name: 'Clear all registries', value: 'clear' },
|
||||
{ name: 'Set access level (public/private)', value: 'access' },
|
||||
{ name: 'Configure commit options', value: 'commit' },
|
||||
{ name: 'Configure services', value: 'services' },
|
||||
{ name: 'Show help', value: 'help' },
|
||||
],
|
||||
});
|
||||
@@ -86,6 +117,12 @@ async function handleInteractiveMenu(): Promise<void> {
|
||||
case 'access':
|
||||
await handleAccessLevel();
|
||||
break;
|
||||
case 'commit':
|
||||
await handleCommit();
|
||||
break;
|
||||
case 'services':
|
||||
await handleServices();
|
||||
break;
|
||||
case 'help':
|
||||
showHelp();
|
||||
break;
|
||||
@@ -149,8 +186,8 @@ async function handleAdd(url?: string): Promise<void> {
|
||||
|
||||
if (added) {
|
||||
await config.save();
|
||||
await runFormatter('npmextra', { silent: true });
|
||||
plugins.logger.log('success', `Added registry: ${url}`);
|
||||
await formatNpmextraWithDiff();
|
||||
} else {
|
||||
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
||||
}
|
||||
@@ -185,8 +222,8 @@ async function handleRemove(url?: string): Promise<void> {
|
||||
|
||||
if (removed) {
|
||||
await config.save();
|
||||
await runFormatter('npmextra', { silent: true });
|
||||
plugins.logger.log('success', `Removed registry: ${url}`);
|
||||
await formatNpmextraWithDiff();
|
||||
} else {
|
||||
plugins.logger.log('warn', `Registry not found: ${url}`);
|
||||
}
|
||||
@@ -212,8 +249,8 @@ async function handleClear(): Promise<void> {
|
||||
if (confirmed) {
|
||||
config.clearRegistries();
|
||||
await config.save();
|
||||
await runFormatter('npmextra', { silent: true });
|
||||
plugins.logger.log('success', 'All registries cleared.');
|
||||
await formatNpmextraWithDiff();
|
||||
} else {
|
||||
plugins.logger.log('info', 'Operation cancelled.');
|
||||
}
|
||||
@@ -252,8 +289,115 @@ async function handleAccessLevel(level?: string): Promise<void> {
|
||||
|
||||
config.setAccessLevel(level as 'public' | 'private');
|
||||
await config.save();
|
||||
await runFormatter('npmextra', { silent: true });
|
||||
plugins.logger.log('success', `Access level set to: ${level}`);
|
||||
await formatNpmextraWithDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle commit configuration
|
||||
*/
|
||||
async function handleCommit(setting?: string, value?: string): Promise<void> {
|
||||
const config = await CommitConfig.fromCwd();
|
||||
|
||||
// No setting = interactive mode
|
||||
if (!setting) {
|
||||
await handleCommitInteractive(config);
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct setting
|
||||
switch (setting) {
|
||||
case 'alwaysTest':
|
||||
await handleCommitSetting(config, 'alwaysTest', value);
|
||||
break;
|
||||
case 'alwaysBuild':
|
||||
await handleCommitSetting(config, 'alwaysBuild', value);
|
||||
break;
|
||||
default:
|
||||
plugins.logger.log('error', `Unknown commit setting: ${setting}`);
|
||||
showCommitHelp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive commit configuration
|
||||
*/
|
||||
async function handleCommitInteractive(config: CommitConfig): Promise<void> {
|
||||
console.log('');
|
||||
console.log('╭─────────────────────────────────────────────────────────────╮');
|
||||
console.log('│ Commit Configuration │');
|
||||
console.log('╰─────────────────────────────────────────────────────────────╯');
|
||||
console.log('');
|
||||
|
||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||
const response = await interactInstance.askQuestion({
|
||||
type: 'checkbox',
|
||||
name: 'commitOptions',
|
||||
message: 'Select commit options to enable:',
|
||||
choices: [
|
||||
{ name: 'Always run tests before commit (-t)', value: 'alwaysTest' },
|
||||
{ name: 'Always build after commit (-b)', value: 'alwaysBuild' },
|
||||
],
|
||||
default: [
|
||||
...(config.getAlwaysTest() ? ['alwaysTest'] : []),
|
||||
...(config.getAlwaysBuild() ? ['alwaysBuild'] : []),
|
||||
],
|
||||
});
|
||||
|
||||
const selected = (response as any).value || [];
|
||||
config.setAlwaysTest(selected.includes('alwaysTest'));
|
||||
config.setAlwaysBuild(selected.includes('alwaysBuild'));
|
||||
await config.save();
|
||||
|
||||
plugins.logger.log('success', 'Commit configuration updated');
|
||||
await formatNpmextraWithDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific commit setting
|
||||
*/
|
||||
async function handleCommitSetting(config: CommitConfig, setting: string, value?: string): Promise<void> {
|
||||
// Parse boolean value
|
||||
const boolValue = value === 'true' || value === '1' || value === 'on';
|
||||
|
||||
if (setting === 'alwaysTest') {
|
||||
config.setAlwaysTest(boolValue);
|
||||
} else if (setting === 'alwaysBuild') {
|
||||
config.setAlwaysBuild(boolValue);
|
||||
}
|
||||
|
||||
await config.save();
|
||||
plugins.logger.log('success', `Set ${setting} to ${boolValue}`);
|
||||
await formatNpmextraWithDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help for commit subcommand
|
||||
*/
|
||||
function showCommitHelp(): void {
|
||||
console.log('');
|
||||
console.log('Usage: gitzone config commit [setting] [value]');
|
||||
console.log('');
|
||||
console.log('Settings:');
|
||||
console.log(' alwaysTest [true|false] Always run tests before commit');
|
||||
console.log(' alwaysBuild [true|false] Always build after commit');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' gitzone config commit # Interactive mode');
|
||||
console.log(' gitzone config commit alwaysTest true');
|
||||
console.log(' gitzone config commit alwaysBuild false');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle services configuration
|
||||
*/
|
||||
async function handleServices(): Promise<void> {
|
||||
// Import and use ServiceManager's configureServices
|
||||
const { ServiceManager } = await import('../mod_services/classes.servicemanager.js');
|
||||
const serviceManager = new ServiceManager();
|
||||
await serviceManager.init();
|
||||
await serviceManager.configureServices();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,6 +413,8 @@ function showHelp(): void {
|
||||
console.log(' remove [url] Remove a registry URL');
|
||||
console.log(' clear Clear all registries');
|
||||
console.log(' access [public|private] Set npm access level for publishing');
|
||||
console.log(' commit [setting] [value] Configure commit options');
|
||||
console.log(' services Configure which services are enabled');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' gitzone config show');
|
||||
@@ -278,5 +424,8 @@ function showHelp(): void {
|
||||
console.log(' gitzone config clear');
|
||||
console.log(' gitzone config access public');
|
||||
console.log(' gitzone config access private');
|
||||
console.log(' gitzone config commit # Interactive');
|
||||
console.log(' gitzone config commit alwaysTest true');
|
||||
console.log(' gitzone config services # Interactive');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { FormatContext } from './classes.formatcontext.js';
|
||||
import type { IPlannedChange } from './interfaces.format.js';
|
||||
import type { IPlannedChange, ICheckResult } from './interfaces.format.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
export abstract class BaseFormatter {
|
||||
@@ -79,4 +79,98 @@ export abstract class BaseFormatter {
|
||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for diffs without applying changes
|
||||
* Returns information about what would change
|
||||
*/
|
||||
async check(): Promise<ICheckResult> {
|
||||
const changes = await this.analyze();
|
||||
const diffs: ICheckResult['diffs'] = [];
|
||||
|
||||
for (const change of changes) {
|
||||
// Skip generic changes that don't have actual content
|
||||
if (change.path === '<various files>') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (change.type === 'modify' || change.type === 'create') {
|
||||
// Read current content if file exists
|
||||
let currentContent: string | undefined;
|
||||
try {
|
||||
currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||
} catch {
|
||||
// File doesn't exist yet
|
||||
currentContent = undefined;
|
||||
}
|
||||
|
||||
const newContent = change.content;
|
||||
|
||||
// Check if there's an actual diff
|
||||
if (currentContent !== newContent && newContent !== undefined) {
|
||||
diffs.push({
|
||||
path: change.path,
|
||||
type: change.type,
|
||||
before: currentContent,
|
||||
after: newContent,
|
||||
});
|
||||
}
|
||||
} else if (change.type === 'delete') {
|
||||
// Check if file exists before marking for deletion
|
||||
try {
|
||||
const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||
diffs.push({
|
||||
path: change.path,
|
||||
type: 'delete',
|
||||
before: currentContent,
|
||||
after: undefined,
|
||||
});
|
||||
} catch {
|
||||
// File doesn't exist, nothing to delete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasDiff: diffs.length > 0,
|
||||
diffs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a single diff using smartdiff
|
||||
*/
|
||||
displayDiff(diff: ICheckResult['diffs'][0]): void {
|
||||
console.log(`\n--- ${diff.path}`);
|
||||
if (diff.before && diff.after) {
|
||||
console.log(plugins.smartdiff.formatUnifiedDiffForConsole(diff.before, diff.after, {
|
||||
originalFileName: diff.path,
|
||||
revisedFileName: diff.path,
|
||||
context: 3,
|
||||
}));
|
||||
} else if (diff.after && !diff.before) {
|
||||
console.log(' (new file)');
|
||||
// Show first few lines of new content
|
||||
const lines = diff.after.split('\n').slice(0, 10);
|
||||
lines.forEach(line => console.log(` + ${line}`));
|
||||
if (diff.after.split('\n').length > 10) {
|
||||
console.log(' ... (truncated)');
|
||||
}
|
||||
} else if (diff.before && !diff.after) {
|
||||
console.log(' (file will be deleted)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all diffs from a check result
|
||||
*/
|
||||
displayAllDiffs(result: ICheckResult): void {
|
||||
if (!result.hasDiff) {
|
||||
console.log(' No changes detected');
|
||||
return;
|
||||
}
|
||||
for (const diff of result.diffs) {
|
||||
this.displayDiff(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,117 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatCopy from '../format.copy.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
export class CopyFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'copy', formatCopy);
|
||||
interface ICopyPattern {
|
||||
from: string;
|
||||
to: string;
|
||||
preservePath?: boolean;
|
||||
}
|
||||
|
||||
export class CopyFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'copy';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
|
||||
// Get copy configuration from npmextra.json
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||
const copyConfig = npmextraConfig.dataFor<{ patterns: ICopyPattern[] }>(
|
||||
'gitzone.format.copy',
|
||||
{ patterns: [] },
|
||||
);
|
||||
|
||||
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
|
||||
logVerbose('No copy patterns configured in npmextra.json');
|
||||
return changes;
|
||||
}
|
||||
|
||||
for (const pattern of copyConfig.patterns) {
|
||||
if (!pattern.from || !pattern.to) {
|
||||
logVerbose('Invalid copy pattern - missing "from" or "to" field');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle glob patterns
|
||||
const entries = await plugins.smartfs
|
||||
.directory('.')
|
||||
.recursive()
|
||||
.filter(pattern.from)
|
||||
.list();
|
||||
const files = entries.map((entry) => entry.path);
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = file;
|
||||
let destPath = pattern.to;
|
||||
|
||||
// If destination is a directory, preserve filename
|
||||
if (pattern.to.endsWith('/')) {
|
||||
const filename = plugins.path.basename(file);
|
||||
destPath = plugins.path.join(pattern.to, filename);
|
||||
}
|
||||
|
||||
// Handle template variables in destination path
|
||||
if (pattern.preservePath) {
|
||||
const relativePath = plugins.path.relative(
|
||||
plugins.path.dirname(pattern.from.replace(/\*/g, '')),
|
||||
file,
|
||||
);
|
||||
destPath = plugins.path.join(pattern.to, relativePath);
|
||||
}
|
||||
|
||||
// Read source content
|
||||
const content = (await plugins.smartfs
|
||||
.file(sourcePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Check if destination exists and has same content
|
||||
let needsCopy = true;
|
||||
const destExists = await plugins.smartfs.file(destPath).exists();
|
||||
if (destExists) {
|
||||
const existingContent = (await plugins.smartfs
|
||||
.file(destPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
if (existingContent === content) {
|
||||
needsCopy = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsCopy) {
|
||||
changes.push({
|
||||
type: destExists ? 'modify' : 'create',
|
||||
path: destPath,
|
||||
module: this.name,
|
||||
description: `Copy from ${sourcePath}`,
|
||||
content: content,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logVerbose(`Failed to process pattern ${pattern.from}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (!change.content) return;
|
||||
|
||||
// Ensure destination directory exists
|
||||
const destDir = plugins.path.dirname(change.path);
|
||||
await plugins.smartfs.directory(destDir).recursive().create();
|
||||
|
||||
if (change.type === 'create') {
|
||||
await this.createFile(change.path, change.content);
|
||||
} else {
|
||||
await this.modifyFile(change.path, change.content);
|
||||
}
|
||||
logger.log('info', `Copied to ${change.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,111 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatGitignore from '../format.gitignore.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger } from '../../gitzone.logging.js';
|
||||
|
||||
export class GitignoreFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'gitignore', formatGitignore);
|
||||
// Standard gitignore template content (without front-matter)
|
||||
const GITIGNORE_TEMPLATE = `.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
#------# custom`;
|
||||
|
||||
export class GitignoreFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'gitignore';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const gitignorePath = '.gitignore';
|
||||
|
||||
// Check if file exists and extract custom content
|
||||
let customContent = '';
|
||||
const exists = await plugins.smartfs.file(gitignorePath).exists();
|
||||
|
||||
if (exists) {
|
||||
const existingContent = (await plugins.smartfs
|
||||
.file(gitignorePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Extract custom section content
|
||||
const customMarkers = ['#------# custom', '# custom'];
|
||||
for (const marker of customMarkers) {
|
||||
const splitResult = existingContent.split(marker);
|
||||
if (splitResult.length > 1) {
|
||||
customContent = splitResult[1].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute new content
|
||||
let newContent = GITIGNORE_TEMPLATE;
|
||||
if (customContent) {
|
||||
newContent = GITIGNORE_TEMPLATE + '\n' + customContent + '\n';
|
||||
} else {
|
||||
newContent = GITIGNORE_TEMPLATE + '\n';
|
||||
}
|
||||
|
||||
// Read current content to compare
|
||||
let currentContent = '';
|
||||
if (exists) {
|
||||
currentContent = (await plugins.smartfs
|
||||
.file(gitignorePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
}
|
||||
|
||||
// Determine change type
|
||||
if (!exists) {
|
||||
changes.push({
|
||||
type: 'create',
|
||||
path: gitignorePath,
|
||||
module: this.name,
|
||||
description: 'Create .gitignore',
|
||||
content: newContent,
|
||||
});
|
||||
} else if (newContent !== currentContent) {
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: gitignorePath,
|
||||
module: this.name,
|
||||
description: 'Update .gitignore (preserving custom section)',
|
||||
content: newContent,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (!change.content) return;
|
||||
|
||||
if (change.type === 'create') {
|
||||
await this.createFile(change.path, change.content);
|
||||
logger.log('info', 'Created .gitignore');
|
||||
} else if (change.type === 'modify') {
|
||||
await this.modifyFile(change.path, change.content);
|
||||
logger.log('info', 'Updated .gitignore (preserved custom section)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import { Project } from '../../classes.project.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
|
||||
// This is a wrapper for existing format modules
|
||||
export class LegacyFormatter extends BaseFormatter {
|
||||
private moduleName: string;
|
||||
private formatModule: any;
|
||||
|
||||
constructor(
|
||||
context: any,
|
||||
project: Project,
|
||||
moduleName: string,
|
||||
formatModule: any,
|
||||
) {
|
||||
super(context, project);
|
||||
this.moduleName = moduleName;
|
||||
this.formatModule = formatModule;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.moduleName;
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
// For legacy modules, we can't easily predict changes
|
||||
// So we'll return a generic change that indicates the module will run
|
||||
return [
|
||||
{
|
||||
type: 'modify',
|
||||
path: '<various files>',
|
||||
module: this.name,
|
||||
description: `Run ${this.name} formatter`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
// Run the legacy format module
|
||||
await this.formatModule.run(this.project);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,62 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatLicense from '../format.license.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import * as paths from '../../paths.js';
|
||||
import { logger } from '../../gitzone.logging.js';
|
||||
|
||||
export class LicenseFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'license', formatLicense);
|
||||
const INCOMPATIBLE_LICENSES: string[] = ['AGPL', 'GPL', 'SSPL'];
|
||||
|
||||
export class LicenseFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'license';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
// License formatter only checks for incompatible licenses
|
||||
// It does not modify any files, so return empty array
|
||||
// The actual check happens in execute() for reporting purposes
|
||||
return [];
|
||||
}
|
||||
|
||||
async execute(changes: IPlannedChange[]): Promise<void> {
|
||||
const startTime = this.stats.moduleStartTime(this.name);
|
||||
this.stats.startModule(this.name);
|
||||
|
||||
try {
|
||||
// Check if node_modules exists
|
||||
const nodeModulesPath = plugins.path.join(paths.cwd, 'node_modules');
|
||||
const nodeModulesExists = await plugins.smartfs
|
||||
.directory(nodeModulesPath)
|
||||
.exists();
|
||||
|
||||
if (!nodeModulesExists) {
|
||||
logger.log('warn', 'No node_modules found. Skipping license check');
|
||||
return;
|
||||
}
|
||||
|
||||
// Run license check
|
||||
const licenseChecker = await plugins.smartlegal.createLicenseChecker();
|
||||
const licenseCheckResult = await licenseChecker.excludeLicenseWithinPath(
|
||||
paths.cwd,
|
||||
INCOMPATIBLE_LICENSES,
|
||||
);
|
||||
|
||||
if (licenseCheckResult.failingModules.length === 0) {
|
||||
logger.log('info', 'License check passed - no incompatible licenses found');
|
||||
} else {
|
||||
logger.log('error', 'License check failed - incompatible licenses found:');
|
||||
for (const failedModule of licenseCheckResult.failingModules) {
|
||||
console.log(
|
||||
` ${failedModule.name} has license ${failedModule.license}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.stats.endModule(this.name, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
// No file changes for license formatter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,165 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatNpmextra from '../format.npmextra.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
export class NpmextraFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'npmextra', formatNpmextra);
|
||||
/**
|
||||
* Migrates npmextra.json from old namespace keys to new package-scoped keys
|
||||
*/
|
||||
const migrateNamespaceKeys = (npmextraJson: any): boolean => {
|
||||
let migrated = false;
|
||||
const migrations = [
|
||||
{ oldKey: 'gitzone', newKey: '@git.zone/cli' },
|
||||
{ oldKey: 'tsdoc', newKey: '@git.zone/tsdoc' },
|
||||
{ oldKey: 'npmdocker', newKey: '@git.zone/tsdocker' },
|
||||
{ oldKey: 'npmci', newKey: '@ship.zone/szci' },
|
||||
{ oldKey: 'szci', newKey: '@ship.zone/szci' },
|
||||
];
|
||||
for (const { oldKey, newKey } of migrations) {
|
||||
if (npmextraJson[oldKey] && !npmextraJson[newKey]) {
|
||||
npmextraJson[newKey] = npmextraJson[oldKey];
|
||||
delete npmextraJson[oldKey];
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
return migrated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel
|
||||
*/
|
||||
const migrateAccessLevel = (npmextraJson: any): boolean => {
|
||||
const szciConfig = npmextraJson['@ship.zone/szci'];
|
||||
|
||||
if (!szciConfig?.npmAccessLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gitzoneConfig = npmextraJson['@git.zone/cli'] || {};
|
||||
if (gitzoneConfig?.release?.accessLevel) {
|
||||
delete szciConfig.npmAccessLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!npmextraJson['@git.zone/cli']) {
|
||||
npmextraJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!npmextraJson['@git.zone/cli'].release) {
|
||||
npmextraJson['@git.zone/cli'].release = {};
|
||||
}
|
||||
|
||||
npmextraJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel;
|
||||
delete szciConfig.npmAccessLevel;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export class NpmextraFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'npmextra';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const npmextraPath = 'npmextra.json';
|
||||
|
||||
// Check if file exists
|
||||
const exists = await plugins.smartfs.file(npmextraPath).exists();
|
||||
if (!exists) {
|
||||
logVerbose('npmextra.json does not exist, skipping');
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const currentContent = (await plugins.smartfs
|
||||
.file(npmextraPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Parse and compute new content
|
||||
const npmextraJson = JSON.parse(currentContent);
|
||||
|
||||
// Apply migrations (these are automatic, non-interactive)
|
||||
migrateNamespaceKeys(npmextraJson);
|
||||
migrateAccessLevel(npmextraJson);
|
||||
|
||||
// Ensure namespaces exist
|
||||
if (!npmextraJson['@git.zone/cli']) {
|
||||
npmextraJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!npmextraJson['@ship.zone/szci']) {
|
||||
npmextraJson['@ship.zone/szci'] = {};
|
||||
}
|
||||
|
||||
const newContent = JSON.stringify(npmextraJson, null, 2);
|
||||
|
||||
// Only add change if content differs
|
||||
if (newContent !== currentContent) {
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: npmextraPath,
|
||||
module: this.name,
|
||||
description: 'Migrate and format npmextra.json',
|
||||
content: newContent,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (change.type !== 'modify' || !change.content) return;
|
||||
|
||||
// Parse the content to check for missing required fields
|
||||
const npmextraJson = JSON.parse(change.content);
|
||||
|
||||
// Check for missing required module information
|
||||
const expectedRepoInformation: string[] = [
|
||||
'projectType',
|
||||
'module.githost',
|
||||
'module.gitscope',
|
||||
'module.gitrepo',
|
||||
'module.description',
|
||||
'module.npmPackagename',
|
||||
'module.license',
|
||||
];
|
||||
|
||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
if (
|
||||
!plugins.smartobject.smartGet(
|
||||
npmextraJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
)
|
||||
) {
|
||||
interactInstance.addQuestions([
|
||||
{
|
||||
message: `What is the value of ${expectedRepoInformationItem}`,
|
||||
name: expectedRepoInformationItem,
|
||||
type: 'input',
|
||||
default: 'undefined variable',
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const answerbucket = await interactInstance.runQueue();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
const cliProvidedValue = answerbucket.getAnswerFor(
|
||||
expectedRepoInformationItem,
|
||||
);
|
||||
if (cliProvidedValue) {
|
||||
plugins.smartobject.smartAdd(
|
||||
npmextraJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
cliProvidedValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the final content
|
||||
const finalContent = JSON.stringify(npmextraJson, null, 2);
|
||||
await this.modifyFile(change.path, finalContent);
|
||||
logger.log('info', 'Updated npmextra.json');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,213 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatPackageJson from '../format.packagejson.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import * as paths from '../../paths.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
export class PackageJsonFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'packagejson', formatPackageJson);
|
||||
/**
|
||||
* Ensures a certain dependency exists or is excluded
|
||||
*/
|
||||
const ensureDependency = async (
|
||||
packageJsonObject: any,
|
||||
position: 'dep' | 'devDep' | 'everywhere',
|
||||
constraint: 'exclude' | 'include' | 'latest',
|
||||
dependencyArg: string,
|
||||
): Promise<void> => {
|
||||
// Parse package name and version, handling scoped packages like @scope/name@version
|
||||
const isScoped = dependencyArg.startsWith('@');
|
||||
const lastAtIndex = dependencyArg.lastIndexOf('@');
|
||||
|
||||
// For scoped packages, the version @ must come after the /
|
||||
// For unscoped packages, any @ indicates a version
|
||||
const hasVersion = isScoped
|
||||
? lastAtIndex > dependencyArg.indexOf('/')
|
||||
: lastAtIndex >= 0;
|
||||
|
||||
const packageName = hasVersion ? dependencyArg.slice(0, lastAtIndex) : dependencyArg;
|
||||
const version = hasVersion ? dependencyArg.slice(lastAtIndex + 1) : 'latest';
|
||||
|
||||
const targetSections: string[] = [];
|
||||
|
||||
switch (position) {
|
||||
case 'dep':
|
||||
targetSections.push('dependencies');
|
||||
break;
|
||||
case 'devDep':
|
||||
targetSections.push('devDependencies');
|
||||
break;
|
||||
case 'everywhere':
|
||||
targetSections.push('dependencies', 'devDependencies');
|
||||
break;
|
||||
}
|
||||
|
||||
for (const section of targetSections) {
|
||||
if (!packageJsonObject[section]) {
|
||||
packageJsonObject[section] = {};
|
||||
}
|
||||
|
||||
switch (constraint) {
|
||||
case 'exclude':
|
||||
delete packageJsonObject[section][packageName];
|
||||
break;
|
||||
case 'include':
|
||||
if (!packageJsonObject[section][packageName]) {
|
||||
packageJsonObject[section][packageName] =
|
||||
version === 'latest' ? '^1.0.0' : version;
|
||||
}
|
||||
break;
|
||||
case 'latest':
|
||||
try {
|
||||
const registry = new plugins.smartnpm.NpmRegistry();
|
||||
const packageInfo = await registry.getPackageInfo(packageName);
|
||||
const latestVersion = packageInfo['dist-tags'].latest;
|
||||
packageJsonObject[section][packageName] = `^${latestVersion}`;
|
||||
} catch (error) {
|
||||
logVerbose(
|
||||
`Could not fetch latest version for ${packageName}, using existing or default`,
|
||||
);
|
||||
if (!packageJsonObject[section][packageName]) {
|
||||
packageJsonObject[section][packageName] =
|
||||
version === 'latest' ? '^1.0.0' : version;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class PackageJsonFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'packagejson';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const packageJsonPath = 'package.json';
|
||||
|
||||
// Check if file exists
|
||||
const exists = await plugins.smartfs.file(packageJsonPath).exists();
|
||||
if (!exists) {
|
||||
logVerbose('package.json does not exist, skipping');
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const currentContent = (await plugins.smartfs
|
||||
.file(packageJsonPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Parse and compute new content
|
||||
const packageJson = JSON.parse(currentContent);
|
||||
|
||||
// Get gitzone config from npmextra
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra(paths.cwd);
|
||||
const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {});
|
||||
|
||||
// Set metadata from gitzone config
|
||||
if (gitzoneData.module) {
|
||||
packageJson.repository = {
|
||||
type: 'git',
|
||||
url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}.git`,
|
||||
};
|
||||
packageJson.bugs = {
|
||||
url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}/issues`,
|
||||
};
|
||||
packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`;
|
||||
}
|
||||
|
||||
// Ensure module type
|
||||
if (!packageJson.type) {
|
||||
packageJson.type = 'module';
|
||||
}
|
||||
|
||||
// Ensure private field exists
|
||||
if (packageJson.private === undefined) {
|
||||
packageJson.private = true;
|
||||
}
|
||||
|
||||
// Ensure license field exists
|
||||
if (!packageJson.license) {
|
||||
packageJson.license = 'UNLICENSED';
|
||||
}
|
||||
|
||||
// Ensure scripts object exists
|
||||
if (!packageJson.scripts) {
|
||||
packageJson.scripts = {};
|
||||
}
|
||||
|
||||
// Ensure build script exists
|
||||
if (!packageJson.scripts.build) {
|
||||
packageJson.scripts.build = `echo "Not needed for now"`;
|
||||
}
|
||||
|
||||
// Ensure buildDocs script exists
|
||||
if (!packageJson.scripts.buildDocs) {
|
||||
packageJson.scripts.buildDocs = `tsdoc`;
|
||||
}
|
||||
|
||||
// Set files array
|
||||
packageJson.files = [
|
||||
'ts/**/*',
|
||||
'ts_web/**/*',
|
||||
'dist/**/*',
|
||||
'dist_*/**/*',
|
||||
'dist_ts/**/*',
|
||||
'dist_ts_web/**/*',
|
||||
'assets/**/*',
|
||||
'cli.js',
|
||||
'npmextra.json',
|
||||
'readme.md',
|
||||
];
|
||||
|
||||
// Handle dependencies
|
||||
await ensureDependency(
|
||||
packageJson,
|
||||
'devDep',
|
||||
'exclude',
|
||||
'@push.rocks/tapbundle',
|
||||
);
|
||||
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest');
|
||||
await ensureDependency(
|
||||
packageJson,
|
||||
'devDep',
|
||||
'latest',
|
||||
'@git.zone/tsbuild',
|
||||
);
|
||||
|
||||
// Set pnpm overrides from assets
|
||||
try {
|
||||
const overridesContent = (await plugins.smartfs
|
||||
.file(plugins.path.join(paths.assetsDir, 'overrides.json'))
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const overrides = JSON.parse(overridesContent);
|
||||
packageJson.pnpm = packageJson.pnpm || {};
|
||||
packageJson.pnpm.overrides = overrides;
|
||||
} catch (error) {
|
||||
logVerbose(`Could not read overrides.json: ${error.message}`);
|
||||
}
|
||||
|
||||
const newContent = JSON.stringify(packageJson, null, 2);
|
||||
|
||||
// Only add change if content differs
|
||||
if (newContent !== currentContent) {
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: packageJsonPath,
|
||||
module: this.name,
|
||||
description: 'Format package.json',
|
||||
content: newContent,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (change.type !== 'modify' || !change.content) return;
|
||||
|
||||
await this.modifyFile(change.path, change.content);
|
||||
logger.log('info', 'Updated package.json');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import type { IPlannedChange, ICheckResult } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
@@ -40,6 +40,7 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
|
||||
// Add files from TypeScript directories
|
||||
for (const dir of includeDirs) {
|
||||
try {
|
||||
const globPattern = `${dir}/**/*.${extensions}`;
|
||||
const dirEntries = await plugins.smartfs
|
||||
.directory('.')
|
||||
@@ -47,20 +48,32 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
.filter(globPattern)
|
||||
.list();
|
||||
const dirFiles = dirEntries.map((entry) => entry.path);
|
||||
allFiles.push(...dirFiles);
|
||||
// Filter out files in excluded directories
|
||||
const filteredFiles = dirFiles.filter((f) =>
|
||||
!f.includes('node_modules/') &&
|
||||
!f.includes('.nogit/') &&
|
||||
!f.includes('.git/')
|
||||
);
|
||||
allFiles.push(...filteredFiles);
|
||||
} catch (error) {
|
||||
logVerbose(`Skipping directory ${dir}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add root config files
|
||||
// Add root config files (only check root level, no recursive needed)
|
||||
for (const pattern of rootConfigFiles) {
|
||||
try {
|
||||
const rootEntries = await plugins.smartfs
|
||||
.directory('.')
|
||||
.recursive()
|
||||
.filter(pattern)
|
||||
.list();
|
||||
const rootFiles = rootEntries.map((entry) => entry.path);
|
||||
// Only include files at root level (no slashes in path)
|
||||
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
|
||||
allFiles.push(...rootLevelFiles);
|
||||
} catch (error) {
|
||||
logVerbose(`Skipping pattern ${pattern}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
@@ -230,4 +243,53 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
arrowParens: 'always',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Override check() to compute diffs on-the-fly by running prettier
|
||||
*/
|
||||
async check(): Promise<ICheckResult> {
|
||||
const changes = await this.analyze();
|
||||
const diffs: ICheckResult['diffs'] = [];
|
||||
|
||||
for (const change of changes) {
|
||||
if (change.type !== 'modify') continue;
|
||||
|
||||
try {
|
||||
// Read current content
|
||||
const currentContent = (await plugins.smartfs
|
||||
.file(change.path)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Skip files without extension (prettier can't infer parser)
|
||||
const fileExt = plugins.path.extname(change.path).toLowerCase();
|
||||
if (!fileExt) continue;
|
||||
|
||||
// Format with prettier to get what it would produce
|
||||
const prettier = await import('prettier');
|
||||
const formatted = await prettier.format(currentContent, {
|
||||
filepath: change.path,
|
||||
...(await this.getPrettierConfig()),
|
||||
});
|
||||
|
||||
// Only add to diffs if content differs
|
||||
if (formatted !== currentContent) {
|
||||
diffs.push({
|
||||
path: change.path,
|
||||
type: 'modify',
|
||||
before: currentContent,
|
||||
after: formatted,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip files that can't be processed
|
||||
logVerbose(`Skipping diff for ${change.path}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasDiff: diffs.length > 0,
|
||||
diffs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as formatReadme from '../format.readme.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger } from '../../gitzone.logging.js';
|
||||
|
||||
const DEFAULT_README_CONTENT = `# Project Readme
|
||||
|
||||
This is the initial readme file.`;
|
||||
|
||||
const DEFAULT_README_HINTS_CONTENT = `# Project Readme Hints
|
||||
|
||||
This is the initial readme hints file.`;
|
||||
|
||||
export class ReadmeFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
@@ -8,17 +17,39 @@ export class ReadmeFormatter extends BaseFormatter {
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
return [
|
||||
{
|
||||
type: 'modify',
|
||||
const changes: IPlannedChange[] = [];
|
||||
|
||||
// Check readme.md
|
||||
const readmeExists = await plugins.smartfs.file('readme.md').exists();
|
||||
if (!readmeExists) {
|
||||
changes.push({
|
||||
type: 'create',
|
||||
path: 'readme.md',
|
||||
module: this.name,
|
||||
description: 'Ensure readme files exist',
|
||||
},
|
||||
];
|
||||
description: 'Create readme.md',
|
||||
content: DEFAULT_README_CONTENT,
|
||||
});
|
||||
}
|
||||
|
||||
// Check readme.hints.md
|
||||
const hintsExists = await plugins.smartfs.file('readme.hints.md').exists();
|
||||
if (!hintsExists) {
|
||||
changes.push({
|
||||
type: 'create',
|
||||
path: 'readme.hints.md',
|
||||
module: this.name,
|
||||
description: 'Create readme.hints.md',
|
||||
content: DEFAULT_README_HINTS_CONTENT,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
await formatReadme.run();
|
||||
if (change.type !== 'create' || !change.content) return;
|
||||
|
||||
await this.createFile(change.path, change.content);
|
||||
logger.log('info', `Created ${change.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,155 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatTemplates from '../format.templates.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import * as paths from '../../paths.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
export class TemplatesFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'templates', formatTemplates);
|
||||
export class TemplatesFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'templates';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const project = this.project;
|
||||
const projectType = project.gitzoneConfig?.data?.projectType;
|
||||
|
||||
// VSCode template - for all projects
|
||||
const vscodeChanges = await this.analyzeTemplate('vscode', [
|
||||
{ templatePath: '.vscode/settings.json', destPath: '.vscode/settings.json' },
|
||||
{ templatePath: '.vscode/launch.json', destPath: '.vscode/launch.json' },
|
||||
]);
|
||||
changes.push(...vscodeChanges);
|
||||
|
||||
// CI and other templates based on projectType
|
||||
switch (projectType) {
|
||||
case 'npm':
|
||||
case 'wcc':
|
||||
const accessLevel = project.gitzoneConfig?.data?.npmciOptions?.npmAccessLevel;
|
||||
const ciTemplate = accessLevel === 'public' ? 'ci_default' : 'ci_default_private';
|
||||
const ciChanges = await this.analyzeTemplate(ciTemplate, [
|
||||
{ templatePath: '.gitea/workflows/default_nottags.yaml', destPath: '.gitea/workflows/default_nottags.yaml' },
|
||||
{ templatePath: '.gitea/workflows/default_tags.yaml', destPath: '.gitea/workflows/default_tags.yaml' },
|
||||
]);
|
||||
changes.push(...ciChanges);
|
||||
break;
|
||||
|
||||
case 'service':
|
||||
case 'website':
|
||||
const dockerCiChanges = await this.analyzeTemplate('ci_docker', [
|
||||
{ templatePath: '.gitea/workflows/docker_nottags.yaml', destPath: '.gitea/workflows/docker_nottags.yaml' },
|
||||
{ templatePath: '.gitea/workflows/docker_tags.yaml', destPath: '.gitea/workflows/docker_tags.yaml' },
|
||||
]);
|
||||
changes.push(...dockerCiChanges);
|
||||
|
||||
const dockerfileChanges = await this.analyzeTemplate('dockerfile_service', [
|
||||
{ templatePath: 'Dockerfile', destPath: 'Dockerfile' },
|
||||
{ templatePath: 'dockerignore', destPath: '.dockerignore' },
|
||||
]);
|
||||
changes.push(...dockerfileChanges);
|
||||
|
||||
const cliChanges = await this.analyzeTemplate('cli', [
|
||||
{ templatePath: 'cli.js', destPath: 'cli.js' },
|
||||
{ templatePath: 'cli.ts.js', destPath: 'cli.ts.js' },
|
||||
]);
|
||||
changes.push(...cliChanges);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update templates based on projectType
|
||||
if (projectType === 'website') {
|
||||
const websiteChanges = await this.analyzeTemplate('website_update', [
|
||||
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
||||
]);
|
||||
changes.push(...websiteChanges);
|
||||
} else if (projectType === 'service') {
|
||||
const serviceChanges = await this.analyzeTemplate('service_update', []);
|
||||
changes.push(...serviceChanges);
|
||||
} else if (projectType === 'wcc') {
|
||||
const wccChanges = await this.analyzeTemplate('wcc_update', [
|
||||
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
||||
{ templatePath: 'html/index.ts', destPath: 'html/index.ts' },
|
||||
]);
|
||||
changes.push(...wccChanges);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private async analyzeTemplate(
|
||||
templateName: string,
|
||||
files: Array<{ templatePath: string; destPath: string }>,
|
||||
): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const templateDir = plugins.path.join(paths.templatesDir, templateName);
|
||||
|
||||
// Check if template exists
|
||||
const templateExists = await plugins.smartfs.directory(templateDir).exists();
|
||||
if (!templateExists) {
|
||||
logVerbose(`Template ${templateName} not found`);
|
||||
return changes;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const templateFilePath = plugins.path.join(templateDir, file.templatePath);
|
||||
const destFilePath = file.destPath;
|
||||
|
||||
// Check if template file exists
|
||||
const fileExists = await plugins.smartfs.file(templateFilePath).exists();
|
||||
if (!fileExists) {
|
||||
logVerbose(`Template file ${templateFilePath} not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read template content
|
||||
const templateContent = (await plugins.smartfs
|
||||
.file(templateFilePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Check if destination file exists
|
||||
const destExists = await plugins.smartfs.file(destFilePath).exists();
|
||||
let currentContent = '';
|
||||
if (destExists) {
|
||||
currentContent = (await plugins.smartfs
|
||||
.file(destFilePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
}
|
||||
|
||||
// Only add change if content differs
|
||||
if (templateContent !== currentContent) {
|
||||
changes.push({
|
||||
type: destExists ? 'modify' : 'create',
|
||||
path: destFilePath,
|
||||
module: this.name,
|
||||
description: `Apply template ${templateName}/${file.templatePath}`,
|
||||
content: templateContent,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logVerbose(`Failed to read template ${templateFilePath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (!change.content) return;
|
||||
|
||||
// Ensure destination directory exists
|
||||
const destDir = plugins.path.dirname(change.path);
|
||||
if (destDir && destDir !== '.') {
|
||||
await plugins.smartfs.directory(destDir).recursive().create();
|
||||
}
|
||||
|
||||
if (change.type === 'create') {
|
||||
await this.createFile(change.path, change.content);
|
||||
} else {
|
||||
await this.modifyFile(change.path, change.content);
|
||||
}
|
||||
logger.log('info', `Applied template to ${change.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,73 @@
|
||||
import { LegacyFormatter } from './legacy.formatter.js';
|
||||
import * as formatTsconfig from '../format.tsconfig.js';
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import * as paths from '../../paths.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
export class TsconfigFormatter extends LegacyFormatter {
|
||||
constructor(context: any, project: any) {
|
||||
super(context, project, 'tsconfig', formatTsconfig);
|
||||
export class TsconfigFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'tsconfig';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const tsconfigPath = 'tsconfig.json';
|
||||
|
||||
// Check if file exists
|
||||
const exists = await plugins.smartfs.file(tsconfigPath).exists();
|
||||
if (!exists) {
|
||||
logVerbose('tsconfig.json does not exist, skipping');
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const currentContent = (await plugins.smartfs
|
||||
.file(tsconfigPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Parse and compute new content
|
||||
const tsconfigObject = JSON.parse(currentContent);
|
||||
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
||||
tsconfigObject.compilerOptions.baseUrl = '.';
|
||||
tsconfigObject.compilerOptions.paths = {};
|
||||
|
||||
// Get module paths from tspublish
|
||||
try {
|
||||
const tsPublishMod = await import('@git.zone/tspublish');
|
||||
const tsPublishInstance = new tsPublishMod.TsPublish();
|
||||
const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
|
||||
|
||||
for (const publishModule of Object.keys(publishModules)) {
|
||||
const publishConfig = publishModules[publishModule];
|
||||
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
|
||||
`./${publishModule}/index.js`,
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
logVerbose(`Could not get tspublish modules: ${error.message}`);
|
||||
}
|
||||
|
||||
const newContent = JSON.stringify(tsconfigObject, null, 2);
|
||||
|
||||
// Only add change if content differs
|
||||
if (newContent !== currentContent) {
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: tsconfigPath,
|
||||
module: this.name,
|
||||
description: 'Format tsconfig.json with path mappings',
|
||||
content: newContent,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (change.type !== 'modify' || !change.content) return;
|
||||
|
||||
await this.modifyFile(change.path, change.content);
|
||||
logger.log('info', 'Updated tsconfig.json');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import { CopyFormatter } from './formatters/copy.formatter.js';
|
||||
|
||||
export let run = async (
|
||||
options: {
|
||||
dryRun?: boolean;
|
||||
write?: boolean; // Explicitly write changes (default: false, dry-mode)
|
||||
dryRun?: boolean; // Deprecated, kept for compatibility
|
||||
yes?: boolean;
|
||||
planOnly?: boolean;
|
||||
savePlan?: string;
|
||||
@@ -28,6 +29,7 @@ export let run = async (
|
||||
interactive?: boolean;
|
||||
parallel?: boolean;
|
||||
verbose?: boolean;
|
||||
diff?: boolean; // Show file diffs
|
||||
} = {},
|
||||
): Promise<any> => {
|
||||
// Set verbose mode if requested
|
||||
@@ -35,7 +37,11 @@ export let run = async (
|
||||
setVerboseMode(true);
|
||||
}
|
||||
|
||||
const project = await Project.fromCwd();
|
||||
// Determine if we should write changes
|
||||
// Default is dry-mode (no writing) unless --write/-w is specified
|
||||
const shouldWrite = options.write ?? (options.dryRun === false);
|
||||
|
||||
const project = await Project.fromCwd({ requireProjectType: false });
|
||||
const context = new FormatContext();
|
||||
// Cache system removed - no longer needed
|
||||
const planner = new FormatPlanner();
|
||||
@@ -127,9 +133,24 @@ export let run = async (
|
||||
return;
|
||||
}
|
||||
|
||||
// Dry-run mode
|
||||
if (options.dryRun) {
|
||||
logger.log('info', 'Dry-run mode - no changes will be made');
|
||||
// Show diffs if requested (works in both dry-run and write modes)
|
||||
if (options.diff) {
|
||||
logger.log('info', 'Showing file diffs:');
|
||||
console.log('');
|
||||
|
||||
for (const formatter of activeFormatters) {
|
||||
const checkResult = await formatter.check();
|
||||
if (checkResult.hasDiff) {
|
||||
logger.log('info', `[${formatter.name}]`);
|
||||
formatter.displayAllDiffs(checkResult);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dry-run mode (default behavior)
|
||||
if (!shouldWrite) {
|
||||
logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,14 +218,27 @@ export const handleCleanBackups = async (): Promise<void> => {
|
||||
);
|
||||
};
|
||||
|
||||
// Import the ICheckResult type for external use
|
||||
import type { ICheckResult } from './interfaces.format.js';
|
||||
export type { ICheckResult };
|
||||
|
||||
// Formatters that don't require projectType to be set
|
||||
const formattersNotRequiringProjectType = ['npmextra', 'prettier', 'cleanup', 'packagejson'];
|
||||
|
||||
/**
|
||||
* Run a single formatter by name (for use by other modules)
|
||||
*/
|
||||
export const runFormatter = async (
|
||||
formatterName: string,
|
||||
options: { silent?: boolean } = {}
|
||||
): Promise<void> => {
|
||||
const project = await Project.fromCwd();
|
||||
options: {
|
||||
silent?: boolean;
|
||||
checkOnly?: boolean; // Only check for diffs, don't apply
|
||||
showDiff?: boolean; // Show the diff output
|
||||
} = {}
|
||||
): Promise<ICheckResult | void> => {
|
||||
// Determine if this formatter requires projectType
|
||||
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
||||
const project = await Project.fromCwd({ requireProjectType });
|
||||
const context = new FormatContext();
|
||||
|
||||
// Map formatter names to classes
|
||||
@@ -227,6 +261,17 @@ export const runFormatter = async (
|
||||
}
|
||||
|
||||
const formatter = new FormatterClass(context, project);
|
||||
|
||||
// Check-only mode: just check for diffs and optionally display them
|
||||
if (options.checkOnly) {
|
||||
const result = await formatter.check();
|
||||
if (result.hasDiff && options.showDiff) {
|
||||
formatter.displayAllDiffs(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Normal mode: analyze and apply changes
|
||||
const changes = await formatter.analyze();
|
||||
|
||||
for (const change of changes) {
|
||||
|
||||
@@ -39,7 +39,18 @@ export type IPlannedChange = {
|
||||
path: string;
|
||||
module: string;
|
||||
description: string;
|
||||
content?: string; // For create/modify operations
|
||||
content?: string; // New content for create/modify operations
|
||||
originalContent?: string; // Original content for comparison
|
||||
diff?: string;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
export interface ICheckResult {
|
||||
hasDiff: boolean;
|
||||
diffs: Array<{
|
||||
path: string;
|
||||
type: 'create' | 'modify' | 'delete';
|
||||
before?: string;
|
||||
after?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user