Compare commits

...

8 Commits

Author SHA1 Message Date
5a663ae767 2.9.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-15 15:25:30 +00:00
218c84a39b update 2025-12-15 15:25:20 +00:00
27d5cdca35 v2.9.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-15 12:00:10 +00:00
3ebf072bfb 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. 2025-12-15 12:00:10 +00:00
08f56ae0a4 v2.8.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-15 06:29:32 +00:00
b2d2684895 feat(commit): Add commit configuration and automatic pre-commit tests 2025-12-15 06:29:32 +00:00
1b328c3045 v2.7.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-14 16:53:18 +00:00
f444a04876 feat(mod_format): Add check-only formatting with interactive diff preview; make formatting default to dry-run and extend formatting API 2025-12-14 16:53:18 +00:00
14 changed files with 603 additions and 69 deletions

View File

@@ -1,5 +1,36 @@
# Changelog # Changelog
## 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) ## 2025-12-14 - 2.6.1 - fix(npmextra)
Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration

View File

@@ -1,7 +1,7 @@
{ {
"name": "@git.zone/cli", "name": "@git.zone/cli",
"private": false, "private": false,
"version": "2.6.1", "version": "2.9.1",
"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.", "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", "main": "dist_ts/index.ts",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
@@ -67,7 +67,7 @@
"@types/node": "^25.0.2" "@types/node": "^25.0.2"
}, },
"dependencies": { "dependencies": {
"@git.zone/tsdoc": "^1.10.2", "@git.zone/tsdoc": "^1.11.2",
"@git.zone/tspublish": "^1.10.3", "@git.zone/tspublish": "^1.10.3",
"@push.rocks/commitinfo": "^1.0.12", "@push.rocks/commitinfo": "^1.0.12",
"@push.rocks/early": "^4.0.4", "@push.rocks/early": "^4.0.4",
@@ -76,7 +76,7 @@
"@push.rocks/npmextra": "^5.3.3", "@push.rocks/npmextra": "^5.3.3",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/smartcli": "^4.0.19", "@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/smartfile": "^13.1.2",
"@push.rocks/smartfs": "^1.2.0", "@push.rocks/smartfs": "^1.2.0",
"@push.rocks/smartgulp": "^3.0.4", "@push.rocks/smartgulp": "^3.0.4",

106
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ importers:
.: .:
dependencies: dependencies:
'@git.zone/tsdoc': '@git.zone/tsdoc':
specifier: ^1.10.2 specifier: ^1.11.2
version: 1.10.2(ws@8.18.3)(zod@3.25.76) version: 1.11.2(ws@8.18.3)(zod@3.25.76)
'@git.zone/tspublish': '@git.zone/tspublish':
specifier: ^1.10.3 specifier: ^1.10.3
version: 1.10.3 version: 1.10.3
@@ -36,8 +36,8 @@ importers:
specifier: ^4.0.19 specifier: ^4.0.19
version: 4.0.19 version: 4.0.19
'@push.rocks/smartdiff': '@push.rocks/smartdiff':
specifier: ^1.0.3 specifier: ^1.1.0
version: 1.0.3 version: 1.1.0
'@push.rocks/smartfile': '@push.rocks/smartfile':
specifier: ^13.1.2 specifier: ^13.1.2
version: 13.1.2 version: 13.1.2
@@ -520,8 +520,8 @@ packages:
resolution: {integrity: sha512-YD1qMYA/4eOuF57V0ccR+xo6ww1+QOYFA2K5gBPFBDNh9VdfvWxxDhOUybja8lT9PVMoli8PHG5WA5tKJkdXIQ==} resolution: {integrity: sha512-YD1qMYA/4eOuF57V0ccR+xo6ww1+QOYFA2K5gBPFBDNh9VdfvWxxDhOUybja8lT9PVMoli8PHG5WA5tKJkdXIQ==}
hasBin: true hasBin: true
'@git.zone/tsdoc@1.10.2': '@git.zone/tsdoc@1.11.2':
resolution: {integrity: sha512-r4pKv74CH0KtzRvGdLioJd3DznSKmr8ZVE43QPFfGSNftH5P2eLAe5lc5nK8gCWb8mgEkb8WNfqtTL3Lkg+XyQ==} resolution: {integrity: sha512-+8bjSm9o9ouljHPAWgZ/ZSIHhHY2oIrN6aEZ3npexjplHwmzcbYH3mvY4FsTOwfoQeZ78i7h+OIzFzxuyMyulg==}
hasBin: true hasBin: true
'@git.zone/tspublish@1.10.3': '@git.zone/tspublish@1.10.3':
@@ -999,12 +999,18 @@ packages:
'@push.rocks/qenv@6.1.3': '@push.rocks/qenv@6.1.3':
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==} resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
'@push.rocks/smartagent@file:../../push.rocks/smartagent':
resolution: {directory: ../../push.rocks/smartagent, type: directory}
'@push.rocks/smartai@0.8.0': '@push.rocks/smartai@0.8.0':
resolution: {integrity: sha512-guzi28meUDc3mydC8kpoA+4pzExRQqygXYFDD4qQSWPpIRHQ7qhpeNqJzrrGezT1yOH5Gb9taPEGwT56hI+nwQ==} resolution: {integrity: sha512-guzi28meUDc3mydC8kpoA+4pzExRQqygXYFDD4qQSWPpIRHQ7qhpeNqJzrrGezT1yOH5Gb9taPEGwT56hI+nwQ==}
'@push.rocks/smartarchive@4.2.4': '@push.rocks/smartarchive@4.2.4':
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==} 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': '@push.rocks/smartarray@1.1.0':
resolution: {integrity: sha512-b5YgBmUdglOJH8zeUf2ZWdPCoqySgwvkycRi2BhA9zVZHkpASh39Ej0q0fxFJetlUVyYqGfVoMVjbVrLFfFV7g==} resolution: {integrity: sha512-b5YgBmUdglOJH8zeUf2ZWdPCoqySgwvkycRi2BhA9zVZHkpASh39Ej0q0fxFJetlUVyYqGfVoMVjbVrLFfFV7g==}
@@ -1042,8 +1048,11 @@ packages:
'@push.rocks/smartdelay@3.0.5': '@push.rocks/smartdelay@3.0.5':
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==} resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
'@push.rocks/smartdiff@1.0.3': '@push.rocks/smartdeno@1.2.0':
resolution: {integrity: sha512-cXUKj0KJBxnrZDN1Ztc2WiFRJM3vOTdQUdBfe6ar5NlKuXytSRMJqVL8IUbtWfMCSOx6HgWAUT7W68+/X2TG8w==} 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': '@push.rocks/smartdns@7.6.1':
resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==} resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==}
@@ -2536,9 +2545,6 @@ packages:
fast-deep-equal@3.1.3: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
fast-fifo@1.3.2: fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
@@ -2707,9 +2713,6 @@ packages:
resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
gpt-tokenizer@3.4.0:
resolution: {integrity: sha512-wxFLnhIXTDjYebd9A9pGl3e31ZpSypbpIJSOswbgop5jLte/AsZVDvjlbEuVFlsqZixVKqbcoNmRlFDf6pz/UQ==}
graceful-fs@4.2.10: graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
@@ -2908,8 +2911,8 @@ packages:
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
isomorphic-git@1.36.0: isomorphic-git@1.36.1:
resolution: {integrity: sha512-22tU165ptowHYoDEwYJy5EKRzpHiuLMliaR01fH9ZwaUj1z/IqE++tGpjw/pD6eCWoxiOp6TPWX434aJ9zA4Lg==} resolution: {integrity: sha512-fC8SRT8MwoaXDK8G4z5biPEbqf2WyEJUb2MJ2ftSd39/UIlsnoZxLGux+lae0poLZO4AEcx6aUVOh5bV+P8zFA==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
@@ -5063,12 +5066,13 @@ snapshots:
- '@swc/helpers' - '@swc/helpers'
- supports-color - supports-color
'@git.zone/tsdoc@1.10.2(ws@8.18.3)(zod@3.25.76)': '@git.zone/tsdoc@1.11.2(ws@8.18.3)(zod@3.25.76)':
dependencies: dependencies:
'@git.zone/tspublish': 1.10.3 '@git.zone/tspublish': 1.10.3
'@push.rocks/early': 4.0.4 '@push.rocks/early': 4.0.4
'@push.rocks/npmextra': 5.3.3 '@push.rocks/npmextra': 5.3.3
'@push.rocks/qenv': 6.1.3 '@push.rocks/qenv': 6.1.3
'@push.rocks/smartagent': file:../../push.rocks/smartagent(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/smartai': 0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
'@push.rocks/smartcli': 4.0.19 '@push.rocks/smartcli': 4.0.19
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
@@ -5081,7 +5085,6 @@ snapshots:
'@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpath': 6.0.0
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.0
'@push.rocks/smarttime': 4.1.1 '@push.rocks/smarttime': 4.1.1
gpt-tokenizer: 3.4.0
typedoc: 0.28.15(typescript@5.9.3) typedoc: 0.28.15(typescript@5.9.3)
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
@@ -5880,6 +5883,29 @@ snapshots:
'@push.rocks/smartlog': 3.1.10 '@push.rocks/smartlog': 3.1.10
'@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpath': 6.0.0
'@push.rocks/smartagent@file:../../push.rocks/smartagent(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
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)': '@push.rocks/smartai@0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
dependencies: dependencies:
'@anthropic-ai/sdk': 0.65.0(zod@3.25.76) '@anthropic-ai/sdk': 0.65.0(zod@3.25.76)
@@ -5926,6 +5952,26 @@ snapshots:
- react-native-b4a - react-native-b4a
- supports-color - 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/smartarray@1.1.0': {}
'@push.rocks/smartbrowser@2.0.8(typescript@5.9.3)': '@push.rocks/smartbrowser@2.0.8(typescript@5.9.3)':
@@ -6049,9 +6095,21 @@ snapshots:
dependencies: dependencies:
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartdiff@1.0.3': '@push.rocks/smartdeno@1.2.0':
dependencies: 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': '@push.rocks/smartdns@7.6.1':
dependencies: dependencies:
@@ -6180,7 +6238,7 @@ snapshots:
'@push.rocks/smarttime': 4.1.1 '@push.rocks/smarttime': 4.1.1
'@types/diff': 8.0.0 '@types/diff': 8.0.0
diff: 8.0.2 diff: 8.0.2
isomorphic-git: 1.36.0 isomorphic-git: 1.36.1
minimatch: 10.1.1 minimatch: 10.1.1
'@push.rocks/smartguard@3.1.0': '@push.rocks/smartguard@3.1.0':
@@ -8145,8 +8203,6 @@ snapshots:
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
fast-diff@1.3.0: {}
fast-fifo@1.3.2: {} fast-fifo@1.3.2: {}
fast-json-stable-stringify@2.1.0: {} fast-json-stable-stringify@2.1.0: {}
@@ -8364,8 +8420,6 @@ snapshots:
p-cancelable: 3.0.0 p-cancelable: 3.0.0
responselike: 3.0.0 responselike: 3.0.0
gpt-tokenizer@3.4.0: {}
graceful-fs@4.2.10: {} graceful-fs@4.2.10: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
@@ -8578,7 +8632,7 @@ snapshots:
isexe@3.1.1: {} isexe@3.1.1: {}
isomorphic-git@1.36.0: isomorphic-git@1.36.1:
dependencies: dependencies:
async-lock: 1.4.1 async-lock: 1.4.1
clean-git-ref: 2.0.1 clean-git-ref: 2.0.1

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/cli', name: '@git.zone/cli',
version: '2.6.1', version: '2.9.0',
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.' 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.'
} }

View File

@@ -8,10 +8,11 @@ import type { TGitzoneProjectType } from './classes.gitzoneconfig.js';
* the Project class is a tool to work with a gitzone project * the Project class is a tool to work with a gitzone project
*/ */
export class Project { export class Project {
public static async fromCwd() { public static async fromCwd(options: { requireProjectType?: boolean } = {}) {
const gitzoneConfig = await GitzoneConfig.fromCwd(); const gitzoneConfig = await GitzoneConfig.fromCwd();
const project = new Project(gitzoneConfig); 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'); throw new Error('Please define a project type');
} }
return project; return project;

View File

@@ -80,7 +80,9 @@ export let run = async () => {
} }
// Handle format with options // Handle format with options
// Default is dry-mode, use --write/-w to apply changes
await modFormat.run({ await modFormat.run({
write: argvArg.write || argvArg.w,
dryRun: argvArg['dry-run'], dryRun: argvArg['dry-run'],
yes: argvArg.yes, yes: argvArg.yes,
planOnly: argvArg['plan-only'], planOnly: argvArg['plan-only'],
@@ -90,6 +92,7 @@ export let run = async () => {
interactive: argvArg.interactive !== false, interactive: argvArg.interactive !== false,
parallel: argvArg.parallel !== false, parallel: argvArg.parallel !== false,
verbose: argvArg.verbose, verbose: argvArg.verbose,
diff: argvArg.diff,
}); });
}); });

View File

@@ -8,9 +8,20 @@ import * as ui from './mod.ui.js';
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js'; import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
export const run = async (argvArg: any) => { 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 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; let releaseConfig: ReleaseConfig | null = null;
if (wantsRelease) { if (wantsRelease) {
@@ -28,6 +39,7 @@ export const run = async (argvArg: any) => {
ui.printExecutionPlan({ ui.printExecutionPlan({
autoAccept: !!(argvArg.y || argvArg.yes), autoAccept: !!(argvArg.y || argvArg.yes),
push: !!(argvArg.p || argvArg.push), push: !!(argvArg.p || argvArg.push),
test: wantsTest,
build: wantsBuild, build: wantsBuild,
release: wantsRelease, release: wantsRelease,
format: !!argvArg.format, format: !!argvArg.format,
@@ -39,6 +51,21 @@ export const run = async (argvArg: any) => {
await formatMod.run(); 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...'); ui.printHeader('🔍 Analyzing repository changes...');
const aidoc = new plugins.tsdoc.AiDoc(); const aidoc = new plugins.tsdoc.AiDoc();
@@ -161,6 +188,7 @@ export const run = async (argvArg: any) => {
} }
// Determine total steps based on options // 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 willPush = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true');
const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries(); const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version

View File

@@ -21,6 +21,7 @@ interface ICommitSummary {
interface IExecutionPlanOptions { interface IExecutionPlanOptions {
autoAccept: boolean; autoAccept: boolean;
push: boolean; push: boolean;
test: boolean;
build: boolean; build: boolean;
release: boolean; release: boolean;
format: boolean; format: boolean;
@@ -64,6 +65,7 @@ export function printExecutionPlan(options: IExecutionPlanOptions): void {
console.log(' Options:'); console.log(' Options:');
console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`); console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`);
console.log(` Push to remote ${options.push ? '✓ enabled (-p)' : '○ disabled'}`); 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(` Build & verify ${options.build ? '✓ enabled (-b)' : '○ disabled'}`);
console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`); console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`);
if (options.format) { if (options.format) {
@@ -77,6 +79,9 @@ export function printExecutionPlan(options: IExecutionPlanOptions): void {
if (options.format) { if (options.format) {
console.log(` ${stepNum++}. Format project files`); console.log(` ${stepNum++}. Format project files`);
} }
if (options.test) {
console.log(` ${stepNum++}. Run tests`);
}
console.log(` ${stepNum++}. Analyze repository changes`); console.log(` ${stepNum++}. Analyze repository changes`);
console.log(` ${stepNum++}. Bake commit info into code`); console.log(` ${stepNum++}. Bake commit info into code`);
console.log(` ${stepNum++}. Generate changelog.md`); console.log(` ${stepNum++}. Generate changelog.md`);

View 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;
}
}

View File

@@ -2,9 +2,32 @@
import * as plugins from './mod.plugins.js'; import * as plugins from './mod.plugins.js';
import { ReleaseConfig } from './classes.releaseconfig.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) => { export const run = async (argvArg: any) => {
const command = argvArg._?.[1]; const command = argvArg._?.[1];
@@ -33,6 +56,12 @@ export const run = async (argvArg: any) => {
case 'accessLevel': case 'accessLevel':
await handleAccessLevel(value); await handleAccessLevel(value);
break; break;
case 'commit':
await handleCommit(argvArg._?.[2], argvArg._?.[3]);
break;
case 'services':
await handleServices();
break;
case 'help': case 'help':
showHelp(); showHelp();
break; break;
@@ -48,7 +77,7 @@ export const run = async (argvArg: any) => {
async function handleInteractiveMenu(): Promise<void> { async function handleInteractiveMenu(): Promise<void> {
console.log(''); console.log('');
console.log('╭─────────────────────────────────────────────────────────────╮'); console.log('╭─────────────────────────────────────────────────────────────╮');
console.log('│ gitzone config - Release Configuration │'); console.log('│ gitzone config - Project Configuration │');
console.log('╰─────────────────────────────────────────────────────────────╯'); console.log('╰─────────────────────────────────────────────────────────────╯');
console.log(''); console.log('');
@@ -64,6 +93,8 @@ async function handleInteractiveMenu(): Promise<void> {
{ name: 'Remove a registry', value: 'remove' }, { name: 'Remove a registry', value: 'remove' },
{ name: 'Clear all registries', value: 'clear' }, { name: 'Clear all registries', value: 'clear' },
{ name: 'Set access level (public/private)', value: 'access' }, { name: 'Set access level (public/private)', value: 'access' },
{ name: 'Configure commit options', value: 'commit' },
{ name: 'Configure services', value: 'services' },
{ name: 'Show help', value: 'help' }, { name: 'Show help', value: 'help' },
], ],
}); });
@@ -86,6 +117,12 @@ async function handleInteractiveMenu(): Promise<void> {
case 'access': case 'access':
await handleAccessLevel(); await handleAccessLevel();
break; break;
case 'commit':
await handleCommit();
break;
case 'services':
await handleServices();
break;
case 'help': case 'help':
showHelp(); showHelp();
break; break;
@@ -149,8 +186,8 @@ async function handleAdd(url?: string): Promise<void> {
if (added) { if (added) {
await config.save(); await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Added registry: ${url}`); plugins.logger.log('success', `Added registry: ${url}`);
await formatNpmextraWithDiff();
} else { } else {
plugins.logger.log('warn', `Registry already exists: ${url}`); plugins.logger.log('warn', `Registry already exists: ${url}`);
} }
@@ -185,8 +222,8 @@ async function handleRemove(url?: string): Promise<void> {
if (removed) { if (removed) {
await config.save(); await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Removed registry: ${url}`); plugins.logger.log('success', `Removed registry: ${url}`);
await formatNpmextraWithDiff();
} else { } else {
plugins.logger.log('warn', `Registry not found: ${url}`); plugins.logger.log('warn', `Registry not found: ${url}`);
} }
@@ -212,8 +249,8 @@ async function handleClear(): Promise<void> {
if (confirmed) { if (confirmed) {
config.clearRegistries(); config.clearRegistries();
await config.save(); await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', 'All registries cleared.'); plugins.logger.log('success', 'All registries cleared.');
await formatNpmextraWithDiff();
} else { } else {
plugins.logger.log('info', 'Operation cancelled.'); plugins.logger.log('info', 'Operation cancelled.');
} }
@@ -252,8 +289,115 @@ async function handleAccessLevel(level?: string): Promise<void> {
config.setAccessLevel(level as 'public' | 'private'); config.setAccessLevel(level as 'public' | 'private');
await config.save(); await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Access level set to: ${level}`); 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(' remove [url] Remove a registry URL');
console.log(' clear Clear all registries'); console.log(' clear Clear all registries');
console.log(' access [public|private] Set npm access level for publishing'); 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('');
console.log('Examples:'); console.log('Examples:');
console.log(' gitzone config show'); console.log(' gitzone config show');
@@ -278,5 +424,8 @@ function showHelp(): void {
console.log(' gitzone config clear'); console.log(' gitzone config clear');
console.log(' gitzone config access public'); console.log(' gitzone config access public');
console.log(' gitzone config access private'); 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(''); console.log('');
} }

View File

@@ -1,6 +1,6 @@
import * as plugins from './mod.plugins.js'; import * as plugins from './mod.plugins.js';
import { FormatContext } from './classes.formatcontext.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'; import { Project } from '../classes.project.js';
export abstract class BaseFormatter { export abstract class BaseFormatter {
@@ -79,4 +79,94 @@ export abstract class BaseFormatter {
protected async shouldProcessFile(filepath: string): Promise<boolean> { protected async shouldProcessFile(filepath: string): Promise<boolean> {
return true; 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.formatLineDiffForConsole(diff.before, diff.after));
} 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);
}
}
} }

View File

@@ -40,6 +40,7 @@ export class PrettierFormatter extends BaseFormatter {
// Add files from TypeScript directories // Add files from TypeScript directories
for (const dir of includeDirs) { for (const dir of includeDirs) {
try {
const globPattern = `${dir}/**/*.${extensions}`; const globPattern = `${dir}/**/*.${extensions}`;
const dirEntries = await plugins.smartfs const dirEntries = await plugins.smartfs
.directory('.') .directory('.')
@@ -47,20 +48,32 @@ export class PrettierFormatter extends BaseFormatter {
.filter(globPattern) .filter(globPattern)
.list(); .list();
const dirFiles = dirEntries.map((entry) => entry.path); 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) { for (const pattern of rootConfigFiles) {
try {
const rootEntries = await plugins.smartfs const rootEntries = await plugins.smartfs
.directory('.') .directory('.')
.recursive()
.filter(pattern) .filter(pattern)
.list(); .list();
const rootFiles = rootEntries.map((entry) => entry.path); const rootFiles = rootEntries.map((entry) => entry.path);
// Only include files at root level (no slashes in path) // Only include files at root level (no slashes in path)
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/')); const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
allFiles.push(...rootLevelFiles); allFiles.push(...rootLevelFiles);
} catch (error) {
logVerbose(`Skipping pattern ${pattern}: ${error.message}`);
}
} }
// Remove duplicates // Remove duplicates

View File

@@ -19,7 +19,8 @@ import { CopyFormatter } from './formatters/copy.formatter.js';
export let run = async ( export let run = async (
options: { options: {
dryRun?: boolean; write?: boolean; // Explicitly write changes (default: false, dry-mode)
dryRun?: boolean; // Deprecated, kept for compatibility
yes?: boolean; yes?: boolean;
planOnly?: boolean; planOnly?: boolean;
savePlan?: string; savePlan?: string;
@@ -28,6 +29,7 @@ export let run = async (
interactive?: boolean; interactive?: boolean;
parallel?: boolean; parallel?: boolean;
verbose?: boolean; verbose?: boolean;
diff?: boolean; // Show file diffs
} = {}, } = {},
): Promise<any> => { ): Promise<any> => {
// Set verbose mode if requested // Set verbose mode if requested
@@ -35,7 +37,11 @@ export let run = async (
setVerboseMode(true); 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(); const context = new FormatContext();
// Cache system removed - no longer needed // Cache system removed - no longer needed
const planner = new FormatPlanner(); const planner = new FormatPlanner();
@@ -127,9 +133,24 @@ export let run = async (
return; return;
} }
// Dry-run mode // Show diffs if requested (works in both dry-run and write modes)
if (options.dryRun) { if (options.diff) {
logger.log('info', 'Dry-run mode - no changes will be made'); 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; 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) * Run a single formatter by name (for use by other modules)
*/ */
export const runFormatter = async ( export const runFormatter = async (
formatterName: string, formatterName: string,
options: { silent?: boolean } = {} options: {
): Promise<void> => { silent?: boolean;
const project = await Project.fromCwd(); 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(); const context = new FormatContext();
// Map formatter names to classes // Map formatter names to classes
@@ -227,6 +261,17 @@ export const runFormatter = async (
} }
const formatter = new FormatterClass(context, project); 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(); const changes = await formatter.analyze();
for (const change of changes) { for (const change of changes) {

View File

@@ -39,7 +39,18 @@ export type IPlannedChange = {
path: string; path: string;
module: string; module: string;
description: 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; diff?: string;
size?: number; size?: number;
}; };
export interface ICheckResult {
hasDiff: boolean;
diffs: Array<{
path: string;
type: 'create' | 'modify' | 'delete';
before?: string;
after?: string;
}>;
}