Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f0928e7c7 | |||
| 26effadcc9 | |||
| c38e94bcf3 | |||
| b9b51f29d1 |
+2
-1
@@ -63,7 +63,8 @@
|
||||
},
|
||||
"docker": {
|
||||
"enabled": false,
|
||||
"images": []
|
||||
"engine": "tsdocker",
|
||||
"patterns": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
## Pending
|
||||
|
||||
|
||||
## 2026-05-13 - 2.19.0
|
||||
|
||||
### Features
|
||||
|
||||
- Delegate Docker release targets to `tsdocker`.
|
||||
- Replace Docker image template publishing with `tsdocker push` execution.
|
||||
- Move Docker registry behavior to `@git.zone/tsdocker` config and validate removed image config as invalid.
|
||||
|
||||
## 2026-05-10 - 2.18.1
|
||||
|
||||
### Fixes
|
||||
|
||||
- Run `gitzone config fix` opencode handoff with inherited terminal I/O.
|
||||
|
||||
## 2026-05-10 - 2.18.0
|
||||
|
||||
### Features
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@git.zone/cli",
|
||||
"private": false,
|
||||
"version": "2.18.0",
|
||||
"version": "2.19.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.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
@@ -87,7 +87,7 @@
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartscaf": "^4.0.21",
|
||||
"@push.rocks/smartshell": "^3.3.7",
|
||||
"@push.rocks/smartshell": "^3.5.0",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/smartupdate": "^2.0.6",
|
||||
"prettier": "^3.8.1"
|
||||
|
||||
Generated
+35
-23
@@ -81,8 +81,8 @@ importers:
|
||||
specifier: ^4.0.21
|
||||
version: 4.0.21
|
||||
'@push.rocks/smartshell':
|
||||
specifier: ^3.3.7
|
||||
version: 3.3.7
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
'@push.rocks/smartunique':
|
||||
specifier: ^3.0.9
|
||||
version: 3.0.9
|
||||
@@ -1063,6 +1063,9 @@ packages:
|
||||
'@push.rocks/smartdelay@3.0.5':
|
||||
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
||||
|
||||
'@push.rocks/smartdelay@3.1.0':
|
||||
resolution: {integrity: sha512-59xveBMbWmbFhh/rqhQnYG/klg/VONG9hV8+RQ7ftqsNRkcmUT+VM5etAbODgAUvsF4lxK+xVR0tbZOo0kGhRQ==}
|
||||
|
||||
'@push.rocks/smartdiff@1.1.0':
|
||||
resolution: {integrity: sha512-AAz/unmko0C+g+60odOoK32PE3Ci3YLoB+zfg1LGLyVRCthcdzjqa1C2Km0MfG7IyJQKPdj8J5HPubtpm3ZeaQ==}
|
||||
|
||||
@@ -1192,6 +1195,9 @@ packages:
|
||||
'@push.rocks/smartpromise@4.2.3':
|
||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||
|
||||
'@push.rocks/smartpromise@4.2.4':
|
||||
resolution: {integrity: sha512-8FUyYt94hOIY9mqHjitn4h69u0jbEtTF2RKKw2DpiTVFjpDTk9gXbVHZ/V+xEcBrN4mrzdQES0OiDmkNPoddEQ==}
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5':
|
||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||
|
||||
@@ -1222,8 +1228,8 @@ packages:
|
||||
'@push.rocks/smartserve@2.0.1':
|
||||
resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==}
|
||||
|
||||
'@push.rocks/smartshell@3.3.7':
|
||||
resolution: {integrity: sha512-b3st2+FjHUVhZZRlXfw93+SQA0UMVlURqe55uVpWdjJX7jeGXTTeszuYygtiR99zC5iZ8WZhGDct3N2L1qc/qw==}
|
||||
'@push.rocks/smartshell@3.5.0':
|
||||
resolution: {integrity: sha512-Hx9TVvC/AWxZsnm1GDb+W4Fe58nf1FkKbSBABUgkxct4XRYugBI2z9Twnjm3R9vdRry8oy0enfR9NPVhisGaGA==}
|
||||
|
||||
'@push.rocks/smartspawn@3.0.3':
|
||||
resolution: {integrity: sha512-DyrGPV69wwOiJgKkyruk5hS3UEGZ99xFAqBE9O2nM8VXCRLbbty3xt1Ug5Z092ZZmJYaaGMSnMw3ijyZJFCT0Q==}
|
||||
@@ -3917,9 +3923,9 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
which@6.0.1:
|
||||
resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
which@7.0.0:
|
||||
resolution: {integrity: sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==}
|
||||
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
|
||||
hasBin: true
|
||||
|
||||
wordwrap@1.0.0:
|
||||
@@ -4802,7 +4808,7 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
typedoc: 0.28.17(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
@@ -4832,7 +4838,7 @@ snapshots:
|
||||
'@push.rocks/smartnpm': 2.0.6
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- aws-crt
|
||||
@@ -4845,7 +4851,7 @@ snapshots:
|
||||
'@git.zone/tsrun@2.0.1':
|
||||
dependencies:
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
tsx: 4.21.0
|
||||
|
||||
'@git.zone/tstest@3.3.2(socks@2.8.7)(typescript@5.9.3)':
|
||||
@@ -4870,7 +4876,7 @@ snapshots:
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
'@push.rocks/smarts3': 5.3.0
|
||||
'@push.rocks/smartserve': 2.0.1
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/smartwatch': 6.3.0
|
||||
'@types/ws': 8.18.1
|
||||
@@ -5590,7 +5596,7 @@ snapshots:
|
||||
'@push.rocks/smartai': 2.0.0(typescript@5.9.3)(ws@8.19.0)(zod@3.25.76)
|
||||
'@push.rocks/smartfs': 1.5.0
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
ai: 6.0.116(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
@@ -5775,6 +5781,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
|
||||
'@push.rocks/smartdelay@3.1.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.2.4
|
||||
|
||||
'@push.rocks/smartdiff@1.1.0':
|
||||
dependencies:
|
||||
diff: 8.0.3
|
||||
@@ -5818,7 +5828,7 @@ snapshots:
|
||||
'@push.rocks/smartexit@2.0.3':
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.3.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartpromise': 4.2.4
|
||||
|
||||
'@push.rocks/smartexpect@2.5.0':
|
||||
dependencies:
|
||||
@@ -5897,7 +5907,7 @@ snapshots:
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@types/diff': 8.0.0
|
||||
@@ -5968,7 +5978,7 @@ snapshots:
|
||||
'@push.rocks/smartmustache': 3.0.2
|
||||
'@push.rocks/smartpnpm': 1.0.6
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
'@tsclass/tsclass': 4.4.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -6159,14 +6169,16 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartpnpm@1.0.6':
|
||||
dependencies:
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
|
||||
'@push.rocks/smartpromise@4.2.3': {}
|
||||
|
||||
'@push.rocks/smartpromise@4.2.4': {}
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
puppeteer: 24.35.0(typescript@5.9.3)
|
||||
tree-kill: 1.2.2
|
||||
transitivePeerDependencies:
|
||||
@@ -6233,7 +6245,7 @@ snapshots:
|
||||
'@push.rocks/smartinteract': 2.0.16
|
||||
'@push.rocks/smartobject': 1.0.12
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartshell': 3.3.7
|
||||
'@push.rocks/smartshell': 3.5.0
|
||||
'@push.rocks/smartyaml': 3.0.4
|
||||
|
||||
'@push.rocks/smartserve@2.0.1':
|
||||
@@ -6249,13 +6261,13 @@ snapshots:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@push.rocks/smartshell@3.3.7':
|
||||
'@push.rocks/smartshell@3.5.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartdelay': 3.1.0
|
||||
'@push.rocks/smartexit': 2.0.3
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartpromise': 4.2.4
|
||||
'@types/which': 3.0.4
|
||||
which: 6.0.1
|
||||
which: 7.0.0
|
||||
|
||||
'@push.rocks/smartspawn@3.0.3':
|
||||
dependencies:
|
||||
@@ -9513,7 +9525,7 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
which@6.0.1:
|
||||
which@7.0.0:
|
||||
dependencies:
|
||||
isexe: 4.0.0
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ Targets decide what happens after that:
|
||||
| --- | --- |
|
||||
| `git` | Pushes the release commit and tags, often triggering remote CI release builds |
|
||||
| `npm` | Publishes the package to configured npm registries |
|
||||
| `docker` | Builds and pushes configured Docker images |
|
||||
| `docker` | Delegates container builds and pushes to `tsdocker` |
|
||||
|
||||
```bash
|
||||
# Preview the resolved release plan
|
||||
@@ -205,7 +205,7 @@ The standard buckets are `Breaking Changes`, `Features`, `Fixes`, `Documentation
|
||||
|
||||
## Configuration
|
||||
|
||||
All CLI config lives under `@git.zone/cli` in `.smartconfig.json`.
|
||||
CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker build and registry behavior lives under `@git.zone/tsdocker` and is used by the Docker release target.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -237,11 +237,21 @@ All CLI config lives under `@git.zone/cli` in `.smartconfig.json`.
|
||||
"alreadyPublished": "success"
|
||||
},
|
||||
"docker": {
|
||||
"enabled": false,
|
||||
"images": []
|
||||
"enabled": true,
|
||||
"engine": "tsdocker",
|
||||
"patterns": [],
|
||||
"cached": true,
|
||||
"parallel": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@git.zone/tsdocker": {
|
||||
"registries": ["registry.gitlab.com"],
|
||||
"registryRepoMap": {
|
||||
"registry.gitlab.com": "myorg/myproject"
|
||||
},
|
||||
"platforms": ["linux/amd64", "linux/arm64"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -252,6 +262,12 @@ NPM registries belong only here:
|
||||
@git.zone/cli.release.targets.npm.registries
|
||||
```
|
||||
|
||||
Docker registries belong only here and should be registry hosts without `http://` or `https://`:
|
||||
|
||||
```text
|
||||
@git.zone/tsdocker.registries
|
||||
```
|
||||
|
||||
Useful config commands:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/cli',
|
||||
version: '2.18.0',
|
||||
version: '2.19.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.'
|
||||
}
|
||||
|
||||
@@ -68,8 +68,10 @@ const migrateToV2 = (smartconfigJson: Record<string, any>): boolean => {
|
||||
migrated = true;
|
||||
}
|
||||
|
||||
if (isPlainObject(releaseConfig.docker) && !isPlainObject(targets.docker)) {
|
||||
targets.docker = releaseConfig.docker;
|
||||
if (isPlainObject(releaseConfig.docker)) {
|
||||
targets.docker = isPlainObject(targets.docker)
|
||||
? { ...releaseConfig.docker, ...targets.docker }
|
||||
: releaseConfig.docker;
|
||||
delete releaseConfig.docker;
|
||||
migrated = true;
|
||||
}
|
||||
@@ -141,11 +143,27 @@ const migrateToV2 = (smartconfigJson: Record<string, any>): boolean => {
|
||||
if (dockerTarget.enabled === undefined) {
|
||||
dockerTarget.enabled = true;
|
||||
}
|
||||
dockerTarget.engine = "tsdocker";
|
||||
}
|
||||
delete releaseConfig.steps;
|
||||
migrated = true;
|
||||
}
|
||||
|
||||
if (isPlainObject(targets.docker)) {
|
||||
if (targets.docker.images) {
|
||||
delete targets.docker.images;
|
||||
migrated = true;
|
||||
}
|
||||
if (targets.docker.engine !== "tsdocker") {
|
||||
targets.docker.engine = "tsdocker";
|
||||
migrated = true;
|
||||
}
|
||||
if (!Array.isArray(targets.docker.patterns)) {
|
||||
targets.docker.patterns = [];
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseConfig.changelog) {
|
||||
delete releaseConfig.changelog;
|
||||
migrated = true;
|
||||
|
||||
+20
-3
@@ -52,7 +52,12 @@ export interface IReleaseNpmTargetConfig {
|
||||
|
||||
export interface IReleaseDockerTargetConfig {
|
||||
enabled?: boolean;
|
||||
images?: string[];
|
||||
engine?: "tsdocker";
|
||||
patterns?: string[];
|
||||
cached?: boolean;
|
||||
parallel?: boolean | number;
|
||||
context?: string;
|
||||
noBuild?: boolean;
|
||||
}
|
||||
|
||||
export interface IReleaseWorkflowConfig {
|
||||
@@ -109,7 +114,12 @@ export interface IResolvedReleaseWorkflow {
|
||||
npmAccessLevel: "public" | "private";
|
||||
npmAlreadyPublished: "success" | "error";
|
||||
dockerEnabled: boolean;
|
||||
dockerImages: string[];
|
||||
dockerEngine: "tsdocker";
|
||||
dockerPatterns: string[];
|
||||
dockerCached: boolean;
|
||||
dockerParallel: boolean | number;
|
||||
dockerContext?: string;
|
||||
dockerNoBuild: boolean;
|
||||
}
|
||||
|
||||
interface ICliWorkflowConfig {
|
||||
@@ -382,6 +392,13 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
||||
npmAccessLevel: npmConfig.accessLevel || "public",
|
||||
npmAlreadyPublished: npmConfig.alreadyPublished || "success",
|
||||
dockerEnabled,
|
||||
dockerImages: dockerConfig.images || [],
|
||||
dockerEngine: "tsdocker",
|
||||
dockerPatterns: Array.isArray(dockerConfig.patterns) ? dockerConfig.patterns : [],
|
||||
dockerCached: dockerConfig.cached ?? false,
|
||||
dockerParallel: dockerConfig.parallel ?? false,
|
||||
dockerContext: typeof dockerConfig.context === "string" && dockerConfig.context.trim()
|
||||
? dockerConfig.context.trim()
|
||||
: undefined,
|
||||
dockerNoBuild: dockerConfig.noBuild ?? false,
|
||||
};
|
||||
};
|
||||
|
||||
+219
-21
@@ -793,7 +793,7 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
||||
choices: [
|
||||
{ name: "git - push branch and tags", value: "git" },
|
||||
{ name: "npm - publish package registries", value: "npm" },
|
||||
{ name: "docker - build and push images", value: "docker" },
|
||||
{ name: "docker - build and push through tsdocker", value: "docker" },
|
||||
],
|
||||
default: getDefaultEnabledTargets(currentTargets),
|
||||
});
|
||||
@@ -860,21 +860,49 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
||||
}
|
||||
|
||||
if (enabledTargets.includes("docker")) {
|
||||
const images = await askValue<string>(interactInstance, {
|
||||
const patterns = await askValue<string>(interactInstance, {
|
||||
type: "input",
|
||||
name: "dockerImages",
|
||||
message: "Docker image templates (comma-separated, supports {{version}}):",
|
||||
default: Array.isArray(currentTargets.docker?.images)
|
||||
? currentTargets.docker.images.join(", ")
|
||||
name: "dockerPatterns",
|
||||
message: "tsdocker Dockerfile patterns (comma-separated, empty means all):",
|
||||
default: Array.isArray(currentTargets.docker?.patterns)
|
||||
? currentTargets.docker.patterns.join(", ")
|
||||
: "",
|
||||
});
|
||||
const cached = await askValue<boolean>(interactInstance, {
|
||||
type: "confirm",
|
||||
name: "dockerCached",
|
||||
message: "Use tsdocker cached builds?",
|
||||
default: currentTargets.docker?.cached ?? false,
|
||||
});
|
||||
const parallel = await askValue<string>(interactInstance, {
|
||||
type: "input",
|
||||
name: "dockerParallel",
|
||||
message: "tsdocker parallel mode (false, true, or concurrency number):",
|
||||
default: formatDockerParallel(currentTargets.docker?.parallel ?? false),
|
||||
});
|
||||
const context = await askValue<string>(interactInstance, {
|
||||
type: "input",
|
||||
name: "dockerContext",
|
||||
message: "Docker context for tsdocker (empty for default):",
|
||||
default: currentTargets.docker?.context || "",
|
||||
});
|
||||
const noBuild = await askValue<boolean>(interactInstance, {
|
||||
type: "confirm",
|
||||
name: "dockerNoBuild",
|
||||
message: "Skip tsdocker build and only push existing local registry images?",
|
||||
default: currentTargets.docker?.noBuild ?? false,
|
||||
});
|
||||
releaseTargets.docker = {
|
||||
...(currentTargets.docker || {}),
|
||||
enabled: true,
|
||||
images: parseCsv(images),
|
||||
engine: "tsdocker",
|
||||
patterns: parseCsv(patterns),
|
||||
cached,
|
||||
parallel: parseDockerParallel(parallel),
|
||||
context: context.trim() || undefined,
|
||||
noBuild,
|
||||
};
|
||||
} else {
|
||||
releaseTargets.docker = { ...(currentTargets.docker || {}), enabled: false };
|
||||
releaseTargets.docker = { enabled: false, engine: "tsdocker" };
|
||||
}
|
||||
|
||||
setCliConfigValueInData(smartconfigData, "schemaVersion", CURRENT_GITZONE_CLI_SCHEMA_VERSION);
|
||||
@@ -959,7 +987,7 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
|
||||
let result: plugins.smartshell.IExecResult;
|
||||
try {
|
||||
result = await smartshellInstance.execSpawn("opencode", opencodeArgs, {
|
||||
passthrough: true,
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to run opencode: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@@ -1043,7 +1071,7 @@ async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
|
||||
await validateDetectedProjectType(cliConfig, findings);
|
||||
|
||||
validateCommitConfig(cliConfig.commit || {}, findings);
|
||||
await validateReleaseConfig(cliConfig.release || {}, findings);
|
||||
await validateReleaseConfig(cliConfig.release || {}, smartconfigData, findings);
|
||||
|
||||
return findings;
|
||||
}
|
||||
@@ -1291,9 +1319,14 @@ function formatTarget(enabled: unknown, targetConfig: any): string {
|
||||
details.push(`registries=${targetConfig.registries.length}`);
|
||||
}
|
||||
if (targetConfig.accessLevel) details.push(`access=${targetConfig.accessLevel}`);
|
||||
if (Array.isArray(targetConfig.images)) {
|
||||
details.push(`images=${targetConfig.images.length}`);
|
||||
if (targetConfig.engine) details.push(`engine=${targetConfig.engine}`);
|
||||
if (Array.isArray(targetConfig.patterns)) {
|
||||
details.push(`patterns=${targetConfig.patterns.length}`);
|
||||
}
|
||||
if (targetConfig.cached) details.push("cached=true");
|
||||
if (targetConfig.parallel) details.push(`parallel=${targetConfig.parallel}`);
|
||||
if (targetConfig.context) details.push(`context=${targetConfig.context}`);
|
||||
if (targetConfig.noBuild) details.push("noBuild=true");
|
||||
return details.length > 0 ? `${state} (${details.join(", ")})` : state;
|
||||
}
|
||||
|
||||
@@ -1338,6 +1371,29 @@ function parseCsv(value: string): string[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
function formatDockerParallel(value: unknown): string {
|
||||
if (value === true) return "true";
|
||||
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
||||
return String(Math.floor(value));
|
||||
}
|
||||
return "false";
|
||||
}
|
||||
|
||||
function parseDockerParallel(value: string): boolean | number {
|
||||
const normalizedValue = value.trim().toLowerCase();
|
||||
if (!normalizedValue || ["false", "no", "off", "0"].includes(normalizedValue)) {
|
||||
return false;
|
||||
}
|
||||
if (["true", "yes", "on"].includes(normalizedValue)) {
|
||||
return true;
|
||||
}
|
||||
const numericValue = Number(normalizedValue);
|
||||
if (Number.isFinite(numericValue) && numericValue > 0) {
|
||||
return Math.floor(numericValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeRegistryUrl(url: string): string {
|
||||
let normalizedUrl = url.trim();
|
||||
if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) {
|
||||
@@ -1391,6 +1447,7 @@ function buildConfigFixPrompt(
|
||||
`- Use schemaVersion ${CURRENT_GITZONE_CLI_SCHEMA_VERSION} for ` +
|
||||
"`@git.zone/cli`.",
|
||||
"- Use target-based release config: `release.targets.git`, `release.targets.npm`, and `release.targets.docker`.",
|
||||
"- Docker release targets must use `release.targets.docker.engine = \"tsdocker\"`; Docker registries belong under `@git.zone/tsdocker`.",
|
||||
"- Keep npm registries only at `@git.zone/cli.release.targets.npm.registries`.",
|
||||
"- Do not add runtime legacy compatibility code. If legacy config exists, migrate it explicitly.",
|
||||
"- Do not commit, release, install dependencies, or modify unrelated files.",
|
||||
@@ -1514,6 +1571,7 @@ function validateCommitConfig(
|
||||
|
||||
async function validateReleaseConfig(
|
||||
releaseConfig: Record<string, any>,
|
||||
smartconfigData: Record<string, any>,
|
||||
findings: IDoctorFinding[],
|
||||
): Promise<void> {
|
||||
const confirmation = releaseConfig.confirmation;
|
||||
@@ -1554,7 +1612,7 @@ async function validateReleaseConfig(
|
||||
const targets = releaseConfig.targets || {};
|
||||
await validateGitTarget(targets.git || {}, findings);
|
||||
await validateNpmTarget(targets.npm || {}, findings);
|
||||
validateDockerTarget(targets.docker || {}, findings);
|
||||
await validateDockerTarget(targets.docker || {}, smartconfigData, findings);
|
||||
}
|
||||
|
||||
async function validateGitTarget(
|
||||
@@ -1718,31 +1776,171 @@ async function validateNpmAuth(
|
||||
}
|
||||
}
|
||||
|
||||
function validateDockerTarget(
|
||||
async function validateDockerTarget(
|
||||
dockerTarget: Record<string, any>,
|
||||
smartconfigData: Record<string, any>,
|
||||
findings: IDoctorFinding[],
|
||||
): void {
|
||||
): Promise<void> {
|
||||
if ("images" in dockerTarget) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "Docker release target still uses removed images config",
|
||||
fix: "Remove release.targets.docker.images and configure @git.zone/tsdocker instead.",
|
||||
});
|
||||
}
|
||||
|
||||
const enabled = dockerTarget.enabled ?? false;
|
||||
if (!enabled) {
|
||||
findings.push({ level: "ok", message: "Docker release target is disabled" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(dockerTarget.images) || dockerTarget.images.length === 0) {
|
||||
if (dockerTarget.engine !== "tsdocker") {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "Docker release target is enabled without images",
|
||||
fix: "Set release.targets.docker.images or disable release.targets.docker.enabled.",
|
||||
message: "Docker release target must use tsdocker",
|
||||
fix: "Set release.targets.docker.engine to tsdocker.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (dockerTarget.patterns !== undefined && !Array.isArray(dockerTarget.patterns)) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "Docker release target patterns must be an array",
|
||||
fix: "Set release.targets.docker.patterns to an array of Dockerfile patterns or remove it.",
|
||||
});
|
||||
}
|
||||
|
||||
if (!isValidDockerParallel(dockerTarget.parallel)) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: `Invalid tsdocker parallel setting: ${formatValue(dockerTarget.parallel)}`,
|
||||
fix: "Use false, true, or a positive concurrency number.",
|
||||
});
|
||||
}
|
||||
|
||||
const tsdockerConfig = smartconfigData["@git.zone/tsdocker"];
|
||||
if (!isPlainObject(tsdockerConfig)) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "Docker release target is enabled but @git.zone/tsdocker config is missing",
|
||||
fix: "Add @git.zone/tsdocker.registries and optional registryRepoMap/platforms config.",
|
||||
});
|
||||
} else {
|
||||
validateTsdockerProjectConfig(tsdockerConfig, findings);
|
||||
}
|
||||
|
||||
await validateTsdockerCommand(findings);
|
||||
|
||||
findings.push({
|
||||
level: "ok",
|
||||
message: `Docker release target uses tsdocker (${formatDockerPatterns(dockerTarget.patterns)})`,
|
||||
});
|
||||
}
|
||||
|
||||
function formatDockerPatterns(patterns: unknown): string {
|
||||
return Array.isArray(patterns) && patterns.length > 0
|
||||
? patterns.map((pattern) => String(pattern)).join(", ")
|
||||
: "all Dockerfiles";
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, any> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isValidDockerParallel(value: unknown): boolean {
|
||||
return value === undefined ||
|
||||
value === false ||
|
||||
value === true ||
|
||||
(typeof value === "number" && Number.isFinite(value) && value > 0);
|
||||
}
|
||||
|
||||
function validateTsdockerProjectConfig(
|
||||
tsdockerConfig: Record<string, any>,
|
||||
findings: IDoctorFinding[],
|
||||
): void {
|
||||
const registries = Array.isArray(tsdockerConfig.registries)
|
||||
? tsdockerConfig.registries
|
||||
: [];
|
||||
if (registries.length === 0) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "@git.zone/tsdocker.registries is empty",
|
||||
fix: "Set @git.zone/tsdocker.registries to registry hosts such as registry.gitlab.com.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const registry of registries) {
|
||||
if (typeof registry !== "string" || !registry.trim()) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: `Invalid tsdocker registry: ${formatValue(registry)}`,
|
||||
fix: "Use registry hosts such as registry.gitlab.com.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (registry.startsWith("http://") || registry.startsWith("https://")) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: `tsdocker registry must not include a protocol: ${registry}`,
|
||||
fix: `Use ${registry.replace(/^https?:\/\//, "")}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const registryRepoMap = tsdockerConfig.registryRepoMap;
|
||||
if (registryRepoMap !== undefined && !isPlainObject(registryRepoMap)) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "@git.zone/tsdocker.registryRepoMap must be an object",
|
||||
});
|
||||
} else if (isPlainObject(registryRepoMap)) {
|
||||
for (const registry of Object.keys(registryRepoMap)) {
|
||||
if (registry.startsWith("http://") || registry.startsWith("https://")) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: `tsdocker registryRepoMap key must not include a protocol: ${registry}`,
|
||||
fix: `Use ${registry.replace(/^https?:\/\//, "")}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findings.push({
|
||||
level: "ok",
|
||||
message: `Docker release target has ${dockerTarget.images.length} image template(s)`,
|
||||
message: `@git.zone/tsdocker has ${registries.length} registries`,
|
||||
});
|
||||
}
|
||||
|
||||
async function validateTsdockerCommand(findings: IDoctorFinding[]): Promise<void> {
|
||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||
executor: "bash",
|
||||
sourceFilePaths: [],
|
||||
});
|
||||
try {
|
||||
const result = await smartshellInstance.execSpawn(
|
||||
"tsdocker",
|
||||
["--version"],
|
||||
{ silent: true, timeout: 8000 },
|
||||
);
|
||||
if (result.exitCode === 0) {
|
||||
findings.push({ level: "ok", message: "tsdocker command is available" });
|
||||
} else {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "tsdocker command is not available",
|
||||
fix: "Install @git.zone/tsdocker globally or make it available on PATH.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
findings.push({
|
||||
level: "error",
|
||||
message: "Could not execute tsdocker",
|
||||
fix: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function validateDetectedProjectType(
|
||||
cliConfig: Record<string, any>,
|
||||
findings: IDoctorFinding[],
|
||||
|
||||
+44
-21
@@ -107,7 +107,7 @@ export const run = async (argvArg: any) => {
|
||||
npmResults.push(...(await runNpmTarget(smartshellInstance, workflow)));
|
||||
}
|
||||
if (workflow.targets.includes("docker")) {
|
||||
dockerResults.push(...(await runDockerTarget(smartshellInstance, workflow, newVersion)));
|
||||
dockerResults.push(...(await runDockerTarget(smartshellInstance, workflow)));
|
||||
}
|
||||
|
||||
printReleaseSummary(newVersion, gitResults, npmResults, dockerResults);
|
||||
@@ -262,31 +262,43 @@ async function runNpmTarget(
|
||||
async function runDockerTarget(
|
||||
smartshellInstance: plugins.smartshell.Smartshell,
|
||||
workflow: IResolvedReleaseWorkflow,
|
||||
newVersion: string,
|
||||
): Promise<ITargetResult[]> {
|
||||
if (!workflow.dockerEnabled) {
|
||||
return [{ target: "docker", status: "skipped", message: "disabled" }];
|
||||
}
|
||||
if (workflow.dockerImages.length === 0) {
|
||||
return [{ target: "docker", status: "failed", message: "no images configured" }];
|
||||
|
||||
const command = buildTsdockerPushCommand(workflow);
|
||||
const result = await smartshellInstance.exec(command);
|
||||
const output = `${result.stdout || ""}\n${(result as any).stderr || ""}\n${(result as any).combinedOutput || ""}`;
|
||||
return [{
|
||||
target: workflow.dockerPatterns.length > 0
|
||||
? `tsdocker:${workflow.dockerPatterns.join(",")}`
|
||||
: "tsdocker",
|
||||
status: result.exitCode === 0 ? "success" : "failed",
|
||||
message: result.exitCode === 0 ? undefined : firstMeaningfulLine(output),
|
||||
}];
|
||||
}
|
||||
|
||||
const results: ITargetResult[] = [];
|
||||
for (const imageTemplate of workflow.dockerImages) {
|
||||
const image = imageTemplate.replaceAll("{{version}}", newVersion);
|
||||
const buildResult = await smartshellInstance.exec(`docker build -t ${shellQuote(image)} .`);
|
||||
if (buildResult.exitCode !== 0) {
|
||||
results.push({ target: image, status: "failed", message: "docker build failed" });
|
||||
continue;
|
||||
function buildTsdockerPushCommand(workflow: IResolvedReleaseWorkflow): string {
|
||||
const commandParts = ["tsdocker", "push"];
|
||||
if (workflow.dockerNoBuild) {
|
||||
commandParts.push("--no-build");
|
||||
}
|
||||
const pushResult = await smartshellInstance.exec(`docker push ${shellQuote(image)}`);
|
||||
results.push({
|
||||
target: image,
|
||||
status: pushResult.exitCode === 0 ? "success" : "failed",
|
||||
message: pushResult.exitCode === 0 ? undefined : "docker push failed",
|
||||
});
|
||||
if (workflow.dockerCached) {
|
||||
commandParts.push("--cached");
|
||||
}
|
||||
return results;
|
||||
if (workflow.dockerParallel === true) {
|
||||
commandParts.push("--parallel");
|
||||
} else if (typeof workflow.dockerParallel === "number" && Number.isFinite(workflow.dockerParallel) && workflow.dockerParallel > 0) {
|
||||
commandParts.push(`--parallel=${Math.floor(workflow.dockerParallel)}`);
|
||||
}
|
||||
if (workflow.dockerContext) {
|
||||
commandParts.push(`--context=${shellQuote(workflow.dockerContext)}`);
|
||||
}
|
||||
for (const pattern of workflow.dockerPatterns) {
|
||||
commandParts.push(shellQuote(pattern));
|
||||
}
|
||||
return commandParts.join(" ");
|
||||
}
|
||||
|
||||
function isAlreadyPublishedOutput(output: string): boolean {
|
||||
@@ -315,11 +327,22 @@ function printReleasePlan(workflow: IResolvedReleaseWorkflow): void {
|
||||
console.log(`npm registries: ${workflow.npmRegistries.length > 0 ? workflow.npmRegistries.join(", ") : "none"}`);
|
||||
}
|
||||
if (workflow.targets.includes("docker")) {
|
||||
console.log(`docker images: ${workflow.dockerImages.length > 0 ? workflow.dockerImages.join(", ") : "none"}`);
|
||||
console.log(`docker engine: ${workflow.dockerEngine}`);
|
||||
console.log(`docker patterns: ${workflow.dockerPatterns.length > 0 ? workflow.dockerPatterns.join(", ") : "all Dockerfiles"}`);
|
||||
console.log(`docker options: ${formatDockerOptions(workflow)}`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
function formatDockerOptions(workflow: IResolvedReleaseWorkflow): string {
|
||||
const options: string[] = [];
|
||||
if (workflow.dockerCached) options.push("cached");
|
||||
if (workflow.dockerParallel) options.push(`parallel=${workflow.dockerParallel === true ? "true" : workflow.dockerParallel}`);
|
||||
if (workflow.dockerNoBuild) options.push("no-build");
|
||||
if (workflow.dockerContext) options.push(`context=${workflow.dockerContext}`);
|
||||
return options.length > 0 ? options.join(", ") : "default";
|
||||
}
|
||||
|
||||
function printReleaseSummary(
|
||||
newVersion: string,
|
||||
gitResults: ITargetResult[],
|
||||
@@ -365,7 +388,7 @@ export function showHelp(mode?: ICliMode): void {
|
||||
{ flag: "-p, --push", description: "Enable the git release target" },
|
||||
{ flag: "--target <names>", description: "Release only selected targets: git,npm,docker" },
|
||||
{ flag: "--npm", description: "Enable the npm release target" },
|
||||
{ flag: "--docker", description: "Enable the Docker release target" },
|
||||
{ flag: "--docker", description: "Enable the tsdocker release target" },
|
||||
{ flag: "--no-publish", description: "Run release core and git target only" },
|
||||
{ flag: "--plan", description: "Show resolved workflow without mutating files" },
|
||||
],
|
||||
@@ -385,7 +408,7 @@ export function showHelp(mode?: ICliMode): void {
|
||||
console.log(" -p, --push Enable the git release target");
|
||||
console.log(" --target <names> Release only selected targets: git,npm,docker");
|
||||
console.log(" --npm Enable the npm release target");
|
||||
console.log(" --docker Enable the Docker release target");
|
||||
console.log(" --docker Enable the tsdocker release target");
|
||||
console.log(" --no-publish Run release core and git target only");
|
||||
console.log(" --major|--minor|--patch Override inferred semver level");
|
||||
console.log(" --plan Show resolved workflow without mutating files");
|
||||
|
||||
Reference in New Issue
Block a user