@git.zone/cli 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/helpers.tsdocker.d.ts +3 -0
- package/dist_ts/helpers.tsdocker.js +16 -0
- package/dist_ts/helpers.workflow.d.ts +6 -0
- package/dist_ts/helpers.workflow.js +38 -1
- package/dist_ts/mod_config/index.js +77 -7
- package/dist_ts/mod_docker/classes.dockerpruner.js +6 -7
- package/dist_ts/mod_release/index.d.ts +4 -0
- package/dist_ts/mod_release/index.js +52 -6
- package/dist_ts/mod_services/classes.dockercontainer.d.ts +37 -0
- package/dist_ts/mod_services/classes.dockercontainer.js +108 -6
- package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +17 -0
- package/dist_ts/mod_services/classes.serviceconfiguration.js +90 -3
- package/dist_ts/mod_services/classes.servicemanager.d.ts +23 -0
- package/dist_ts/mod_services/classes.servicemanager.js +374 -89
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +5 -5
- package/readme.md +33 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers.tsdocker.ts +30 -0
- package/ts/helpers.workflow.ts +46 -0
- package/ts/mod_config/index.ts +95 -7
- package/ts/mod_docker/classes.dockerpruner.ts +6 -7
- package/ts/mod_release/index.ts +67 -4
- package/ts/mod_services/classes.dockercontainer.ts +149 -5
- package/ts/mod_services/classes.serviceconfiguration.ts +110 -2
- package/ts/mod_services/classes.servicemanager.ts +550 -112
- package/ts/plugins.ts +2 -0
package/dist_ts/plugins.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-loca
|
|
|
3
3
|
import * as smartconfig from '@push.rocks/smartconfig';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as fs from 'node:fs/promises';
|
|
6
|
+
import * as crypto from 'node:crypto';
|
|
6
7
|
import * as projectinfo from '@push.rocks/projectinfo';
|
|
7
8
|
import * as smartcli from '@push.rocks/smartcli';
|
|
8
9
|
import * as smartpath from '@push.rocks/smartpath';
|
|
@@ -15,4 +16,4 @@ import { SmartFs } from '@push.rocks/smartfs';
|
|
|
15
16
|
import * as smartinteract from '@push.rocks/smartinteract';
|
|
16
17
|
import * as smartdelay from '@push.rocks/smartdelay';
|
|
17
18
|
export declare const smartfs: SmartFs;
|
|
18
|
-
export { smartlog, smartlogDestinationLocal, smartconfig, path, fs, projectinfo, smartcli, smartpath, smartpromise, smartupdate, smartshell, smartnetwork, smartfile, smartinteract, smartdelay, };
|
|
19
|
+
export { smartlog, smartlogDestinationLocal, smartconfig, path, fs, crypto, projectinfo, smartcli, smartpath, smartpromise, smartupdate, smartshell, smartnetwork, smartfile, smartinteract, smartdelay, };
|
package/dist_ts/plugins.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-loca
|
|
|
3
3
|
import * as smartconfig from '@push.rocks/smartconfig';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as fs from 'node:fs/promises';
|
|
6
|
+
import * as crypto from 'node:crypto';
|
|
6
7
|
import * as projectinfo from '@push.rocks/projectinfo';
|
|
7
8
|
import * as smartcli from '@push.rocks/smartcli';
|
|
8
9
|
import * as smartpath from '@push.rocks/smartpath';
|
|
@@ -16,5 +17,5 @@ import * as smartinteract from '@push.rocks/smartinteract';
|
|
|
16
17
|
import * as smartdelay from '@push.rocks/smartdelay';
|
|
17
18
|
// Create smartfs instance for filesystem operations
|
|
18
19
|
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
|
19
|
-
export { smartlog, smartlogDestinationLocal, smartconfig, path, fs, projectinfo, smartcli, smartpath, smartpromise, smartupdate, smartshell, smartnetwork, smartfile, smartinteract, smartdelay, };
|
|
20
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
20
|
+
export { smartlog, smartlogDestinationLocal, smartconfig, path, fs, crypto, projectinfo, smartcli, smartpath, smartpromise, smartupdate, smartshell, smartnetwork, smartfile, smartinteract, smartdelay, };
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssd0JBQXdCLE1BQU0sd0NBQXdDLENBQUM7QUFDbkYsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFDdkQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssVUFBVSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDbkUsT0FBTyxLQUFLLGFBQWEsTUFBTSwyQkFBMkIsQ0FBQztBQUMzRCxPQUFPLEtBQUssVUFBVSxNQUFNLHdCQUF3QixDQUFDO0FBRXJELG9EQUFvRDtBQUNwRCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsSUFBSSxtQkFBbUIsRUFBRSxDQUFDLENBQUM7QUFFOUQsT0FBTyxFQUNMLFFBQVEsRUFDUix3QkFBd0IsRUFDeEIsV0FBVyxFQUNYLElBQUksRUFDSixFQUFFLEVBQ0YsTUFBTSxFQUNOLFdBQVcsRUFDWCxRQUFRLEVBQ1IsU0FBUyxFQUNULFlBQVksRUFDWixXQUFXLEVBQ1gsVUFBVSxFQUNWLFlBQVksRUFDWixTQUFTLEVBQ1QsYUFBYSxFQUNiLFVBQVUsR0FDWCxDQUFDIn0=
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@git.zone/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.0
|
|
4
|
+
"version": "3.1.0",
|
|
5
5
|
"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.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
7
7
|
"typings": "dist_ts/index.d.ts",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"homepage": "https://code.foss.global/git.zone/cli#README",
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@git.zone/tsbuild": "^4.4.2",
|
|
43
|
-
"@git.zone/tsrun": "^2.0.
|
|
44
|
-
"@git.zone/tstest": "^3.6.
|
|
45
|
-
"@types/node": "^
|
|
43
|
+
"@git.zone/tsrun": "^2.0.6",
|
|
44
|
+
"@git.zone/tstest": "^3.6.7",
|
|
45
|
+
"@types/node": "^26.1.1"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@git.zone/tsdoc": "^2.1.1",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@push.rocks/smartpath": "^6.0.0",
|
|
70
70
|
"@push.rocks/smartpromise": "^4.2.3",
|
|
71
71
|
"@push.rocks/smartscaf": "^4.0.22",
|
|
72
|
-
"@push.rocks/smartshell": "^
|
|
72
|
+
"@push.rocks/smartshell": "^4.0.0",
|
|
73
73
|
"@push.rocks/smartunique": "^3.0.9",
|
|
74
74
|
"@push.rocks/smartupdate": "^2.0.7",
|
|
75
75
|
"prettier": "^3.8.1"
|
package/readme.md
CHANGED
|
@@ -243,6 +243,8 @@ CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker b
|
|
|
243
243
|
"docker": {
|
|
244
244
|
"enabled": true,
|
|
245
245
|
"engine": "tsdocker",
|
|
246
|
+
"registry": "registry.gitlab.com",
|
|
247
|
+
"buildRegistries": ["registry.gitlab.com"],
|
|
246
248
|
"patterns": [],
|
|
247
249
|
"cached": true,
|
|
248
250
|
"parallel": true
|
|
@@ -266,12 +268,28 @@ NPM registries belong only here:
|
|
|
266
268
|
@git.zone/cli.release.targets.npm.registries
|
|
267
269
|
```
|
|
268
270
|
|
|
269
|
-
Docker
|
|
271
|
+
Canonical Docker destinations and repository mappings belong here. Registry
|
|
272
|
+
values should be hosts without `http://` or `https://`:
|
|
270
273
|
|
|
271
274
|
```text
|
|
272
275
|
@git.zone/tsdocker.registries
|
|
273
276
|
```
|
|
274
277
|
|
|
278
|
+
The Docker release target runs the project's pinned `@git.zone/tsdocker` through
|
|
279
|
+
`pnpm exec`; install it in each Docker-producing project:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
pnpm add --save-dev @git.zone/tsdocker
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Set `release.targets.docker.registry` to select one destination already declared
|
|
286
|
+
by the tSDocker config when a release must not publish to every configured
|
|
287
|
+
registry.
|
|
288
|
+
Set `release.targets.docker.buildRegistries` when the build should authenticate
|
|
289
|
+
only the configured registries used by private base images; an empty list keeps
|
|
290
|
+
the compatibility default of authenticating every configured registry. Bounded
|
|
291
|
+
build authentication requires project-local `@git.zone/tsdocker` 3.3.0 or newer.
|
|
292
|
+
|
|
275
293
|
Useful config commands:
|
|
276
294
|
|
|
277
295
|
```bash
|
|
@@ -458,6 +476,20 @@ gitzone services logs mongo 50
|
|
|
458
476
|
|
|
459
477
|
Service config is stored in `.nogit/env.json`. Data is stored below `.nogit/`, so it stays out of Git.
|
|
460
478
|
|
|
479
|
+
GitZone derives new MinIO bucket names from the project name using S3 naming
|
|
480
|
+
rules. When loading older configuration it repairs only the exact generated
|
|
481
|
+
`<project>-documents` value if that value is invalid; custom bucket names are
|
|
482
|
+
never rewritten. `services start minio` validates custom names, recreates a
|
|
483
|
+
container whose configured credentials or ports drifted, authenticates the
|
|
484
|
+
configured alias, creates the bucket idempotently, and verifies it before
|
|
485
|
+
reporting success. Before GitZone starts, recreates, or executes inside an
|
|
486
|
+
existing MinIO container, current-project ownership must be proven by exact
|
|
487
|
+
GitZone labels or one unambiguous legacy registry claim. Every lifecycle
|
|
488
|
+
operation after discovery uses the container's immutable id. If startup fails,
|
|
489
|
+
GitZone stops only containers that this invocation started; it never stops a
|
|
490
|
+
container that was already running. Invalid custom names do not block read-only
|
|
491
|
+
or recovery commands such as `status`, `logs`, `stop`, `remove`, and `clean`.
|
|
492
|
+
|
|
461
493
|
### Consuming a service programmatically
|
|
462
494
|
|
|
463
495
|
`gitzone services status --json` emits only JSON on stdout, so a test suite or
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@git.zone/cli',
|
|
6
|
-
version: '3.0
|
|
6
|
+
version: '3.1.0',
|
|
7
7
|
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
8
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
export type TTsdockerBinaryAccess = (binaryPathArg: string) => Promise<void>;
|
|
4
|
+
|
|
5
|
+
export const getProjectTsdockerBinPath = (
|
|
6
|
+
projectRootArg: string,
|
|
7
|
+
platformArg: NodeJS.Platform = process.platform,
|
|
8
|
+
): string => {
|
|
9
|
+
return plugins.path.join(
|
|
10
|
+
projectRootArg,
|
|
11
|
+
'node_modules',
|
|
12
|
+
'.bin',
|
|
13
|
+
platformArg === 'win32' ? 'tsdocker.CMD' : 'tsdocker',
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const hasInstalledProjectTsdockerBinary = async (
|
|
18
|
+
projectRootArg: string,
|
|
19
|
+
platformArg: NodeJS.Platform = process.platform,
|
|
20
|
+
accessArg: TTsdockerBinaryAccess = async (binaryPathArg) => {
|
|
21
|
+
await plugins.fs.access(binaryPathArg);
|
|
22
|
+
},
|
|
23
|
+
): Promise<boolean> => {
|
|
24
|
+
try {
|
|
25
|
+
await accessArg(getProjectTsdockerBinPath(projectRootArg, platformArg));
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
package/ts/helpers.workflow.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface IReleaseNpmTargetConfig {
|
|
|
53
53
|
export interface IReleaseDockerTargetConfig {
|
|
54
54
|
enabled?: boolean;
|
|
55
55
|
engine?: "tsdocker";
|
|
56
|
+
registry?: string;
|
|
57
|
+
buildRegistries?: string[];
|
|
56
58
|
patterns?: string[];
|
|
57
59
|
cached?: boolean;
|
|
58
60
|
parallel?: boolean | number;
|
|
@@ -116,6 +118,8 @@ export interface IResolvedReleaseWorkflow {
|
|
|
116
118
|
npmAlreadyPublished: "success" | "error";
|
|
117
119
|
dockerEnabled: boolean;
|
|
118
120
|
dockerEngine: "tsdocker";
|
|
121
|
+
dockerRegistry?: string;
|
|
122
|
+
dockerBuildRegistries: string[];
|
|
119
123
|
dockerPatterns: string[];
|
|
120
124
|
dockerCached: boolean;
|
|
121
125
|
dockerParallel: boolean | number;
|
|
@@ -145,6 +149,39 @@ const unique = <T>(items: T[]): T[] => {
|
|
|
145
149
|
return result;
|
|
146
150
|
};
|
|
147
151
|
|
|
152
|
+
export const supportsBoundedBuildRegistryAuthentication = (
|
|
153
|
+
versionOutputArg: string,
|
|
154
|
+
): boolean => {
|
|
155
|
+
const match = versionOutputArg.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
156
|
+
if (!match) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const major = Number(match[1]);
|
|
160
|
+
const minor = Number(match[2]);
|
|
161
|
+
return major > 3 || (major === 3 && minor >= 3);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const isStringRecord = (valueArg: unknown): valueArg is Record<string, unknown> => {
|
|
165
|
+
return typeof valueArg === "object" && valueArg !== null && !Array.isArray(valueArg);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const getDeclaredTsdockerRange = (packageJsonArg: unknown): string | undefined => {
|
|
169
|
+
if (!isStringRecord(packageJsonArg)) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
for (const dependencyField of ["devDependencies", "dependencies"] as const) {
|
|
173
|
+
const dependencies = packageJsonArg[dependencyField];
|
|
174
|
+
if (!isStringRecord(dependencies)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const versionRange = dependencies["@git.zone/tsdocker"];
|
|
178
|
+
if (typeof versionRange === "string" && versionRange.trim()) {
|
|
179
|
+
return versionRange.trim();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
};
|
|
184
|
+
|
|
148
185
|
const normalizeConfirmation = (
|
|
149
186
|
value: unknown,
|
|
150
187
|
fallback: TConfirmationMode,
|
|
@@ -395,6 +432,15 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
395
432
|
npmAlreadyPublished: npmConfig.alreadyPublished || "success",
|
|
396
433
|
dockerEnabled,
|
|
397
434
|
dockerEngine: "tsdocker",
|
|
435
|
+
dockerRegistry: typeof dockerConfig.registry === "string" && dockerConfig.registry.trim()
|
|
436
|
+
? dockerConfig.registry.trim()
|
|
437
|
+
: undefined,
|
|
438
|
+
dockerBuildRegistries: Array.isArray(dockerConfig.buildRegistries)
|
|
439
|
+
? dockerConfig.buildRegistries
|
|
440
|
+
.filter((registryArg): registryArg is string => typeof registryArg === "string")
|
|
441
|
+
.map((registryArg) => registryArg.trim())
|
|
442
|
+
.filter(Boolean)
|
|
443
|
+
: [],
|
|
398
444
|
dockerPatterns: Array.isArray(dockerConfig.patterns) ? dockerConfig.patterns : [],
|
|
399
445
|
dockerCached: dockerConfig.cached ?? false,
|
|
400
446
|
dockerParallel: dockerConfig.parallel ?? false,
|
package/ts/mod_config/index.ts
CHANGED
|
@@ -19,6 +19,11 @@ import {
|
|
|
19
19
|
CURRENT_GITZONE_CLI_SCHEMA_VERSION,
|
|
20
20
|
migrateSmartconfigData,
|
|
21
21
|
} from "../helpers.smartconfigmigrations.js";
|
|
22
|
+
import {
|
|
23
|
+
getDeclaredTsdockerRange,
|
|
24
|
+
supportsBoundedBuildRegistryAuthentication,
|
|
25
|
+
} from "../helpers.workflow.js";
|
|
26
|
+
import { hasInstalledProjectTsdockerBinary } from "../helpers.tsdocker.js";
|
|
22
27
|
|
|
23
28
|
export { ReleaseConfig, CommitConfig };
|
|
24
29
|
|
|
@@ -871,6 +876,20 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
|
|
871
876
|
}
|
|
872
877
|
|
|
873
878
|
if (enabledTargets.includes("docker")) {
|
|
879
|
+
const registry = await askValue<string>(interactInstance, {
|
|
880
|
+
type: "input",
|
|
881
|
+
name: "dockerRegistry",
|
|
882
|
+
message: "Single tsdocker registry target (empty means all configured registries):",
|
|
883
|
+
default: currentTargets.docker?.registry || "",
|
|
884
|
+
});
|
|
885
|
+
const buildRegistries = await askValue<string>(interactInstance, {
|
|
886
|
+
type: "input",
|
|
887
|
+
name: "dockerBuildRegistries",
|
|
888
|
+
message: "Registries to authenticate before Docker builds (comma-separated, empty means all):",
|
|
889
|
+
default: Array.isArray(currentTargets.docker?.buildRegistries)
|
|
890
|
+
? currentTargets.docker.buildRegistries.join(", ")
|
|
891
|
+
: "",
|
|
892
|
+
});
|
|
874
893
|
const patterns = await askValue<string>(interactInstance, {
|
|
875
894
|
type: "input",
|
|
876
895
|
name: "dockerPatterns",
|
|
@@ -906,6 +925,8 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
|
|
906
925
|
releaseTargets.docker = {
|
|
907
926
|
enabled: true,
|
|
908
927
|
engine: "tsdocker",
|
|
928
|
+
registry: registry.trim() || undefined,
|
|
929
|
+
buildRegistries: parseCsv(buildRegistries),
|
|
909
930
|
patterns: parseCsv(patterns),
|
|
910
931
|
cached,
|
|
911
932
|
parallel: parseDockerParallel(parallel),
|
|
@@ -1296,6 +1317,7 @@ function parseConfigValue(rawValue: string): any {
|
|
|
1296
1317
|
) {
|
|
1297
1318
|
return JSON.parse(trimmedValue);
|
|
1298
1319
|
}
|
|
1320
|
+
|
|
1299
1321
|
return rawValue;
|
|
1300
1322
|
}
|
|
1301
1323
|
|
|
@@ -1963,6 +1985,36 @@ async function validateDockerTarget(
|
|
|
1963
1985
|
});
|
|
1964
1986
|
}
|
|
1965
1987
|
|
|
1988
|
+
if (
|
|
1989
|
+
dockerTarget.registry !== undefined
|
|
1990
|
+
&& (typeof dockerTarget.registry !== "string"
|
|
1991
|
+
|| !dockerTarget.registry.trim()
|
|
1992
|
+
|| /^https?:\/\//.test(dockerTarget.registry))
|
|
1993
|
+
) {
|
|
1994
|
+
findings.push({
|
|
1995
|
+
level: "error",
|
|
1996
|
+
message: `Invalid tsdocker release registry: ${formatValue(dockerTarget.registry)}`,
|
|
1997
|
+
fix: "Use one configured registry host without a protocol, or remove release.targets.docker.registry.",
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
if (
|
|
2002
|
+
dockerTarget.buildRegistries !== undefined
|
|
2003
|
+
&& (!Array.isArray(dockerTarget.buildRegistries)
|
|
2004
|
+
|| dockerTarget.buildRegistries.some((registryArg: unknown) =>
|
|
2005
|
+
typeof registryArg !== "string"
|
|
2006
|
+
|| !registryArg.trim()
|
|
2007
|
+
|| /^https?:\/\//.test(registryArg)
|
|
2008
|
+
)
|
|
2009
|
+
|| new Set(dockerTarget.buildRegistries).size !== dockerTarget.buildRegistries.length)
|
|
2010
|
+
) {
|
|
2011
|
+
findings.push({
|
|
2012
|
+
level: "error",
|
|
2013
|
+
message: "Invalid tsdocker build registry authentication list",
|
|
2014
|
+
fix: "Use unique configured registry hosts without protocols, or remove release.targets.docker.buildRegistries.",
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
|
|
1966
2018
|
if (!isValidDockerParallel(dockerTarget.parallel)) {
|
|
1967
2019
|
findings.push({
|
|
1968
2020
|
level: "error",
|
|
@@ -1982,7 +2034,7 @@ async function validateDockerTarget(
|
|
|
1982
2034
|
validateTsdockerProjectConfig(tsdockerConfig, findings);
|
|
1983
2035
|
}
|
|
1984
2036
|
|
|
1985
|
-
await validateTsdockerCommand(findings);
|
|
2037
|
+
await validateTsdockerCommand(findings, dockerTarget.buildRegistries);
|
|
1986
2038
|
|
|
1987
2039
|
findings.push({
|
|
1988
2040
|
level: "ok",
|
|
@@ -2064,30 +2116,66 @@ function validateTsdockerProjectConfig(
|
|
|
2064
2116
|
});
|
|
2065
2117
|
}
|
|
2066
2118
|
|
|
2067
|
-
async function validateTsdockerCommand(
|
|
2119
|
+
async function validateTsdockerCommand(
|
|
2120
|
+
findings: IDoctorFinding[],
|
|
2121
|
+
buildRegistriesArg: unknown,
|
|
2122
|
+
): Promise<void> {
|
|
2123
|
+
let packageJson: unknown;
|
|
2124
|
+
try {
|
|
2125
|
+
packageJson = JSON.parse(
|
|
2126
|
+
await plugins.fs.readFile(plugins.path.join(process.cwd(), "package.json"), "utf8"),
|
|
2127
|
+
);
|
|
2128
|
+
} catch {
|
|
2129
|
+
packageJson = undefined;
|
|
2130
|
+
}
|
|
2131
|
+
if (!getDeclaredTsdockerRange(packageJson)) {
|
|
2132
|
+
findings.push({
|
|
2133
|
+
level: "error",
|
|
2134
|
+
message: "Docker release target requires project-local @git.zone/tsdocker",
|
|
2135
|
+
fix: "Run pnpm add --save-dev @git.zone/tsdocker.",
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
if (!(await hasInstalledProjectTsdockerBinary(process.cwd()))) {
|
|
2139
|
+
findings.push({
|
|
2140
|
+
level: "error",
|
|
2141
|
+
message: "Project-local tSDocker binary is not installed",
|
|
2142
|
+
fix: "Run pnpm install after declaring @git.zone/tsdocker.",
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2068
2145
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
2069
2146
|
executor: "bash",
|
|
2070
2147
|
sourceFilePaths: [],
|
|
2071
2148
|
});
|
|
2072
2149
|
try {
|
|
2073
2150
|
const result = await smartshellInstance.execSpawn(
|
|
2074
|
-
"
|
|
2075
|
-
["--version"],
|
|
2151
|
+
"pnpm",
|
|
2152
|
+
["exec", "tsdocker", "--version"],
|
|
2076
2153
|
{ silent: true, timeout: 8000 },
|
|
2077
2154
|
);
|
|
2078
2155
|
if (result.exitCode === 0) {
|
|
2079
2156
|
findings.push({ level: "ok", message: "tsdocker command is available" });
|
|
2157
|
+
if (
|
|
2158
|
+
Array.isArray(buildRegistriesArg)
|
|
2159
|
+
&& buildRegistriesArg.length > 0
|
|
2160
|
+
&& !supportsBoundedBuildRegistryAuthentication(result.combinedOutput)
|
|
2161
|
+
) {
|
|
2162
|
+
findings.push({
|
|
2163
|
+
level: "error",
|
|
2164
|
+
message: "Bounded Docker build authentication requires @git.zone/tsdocker >= 3.3.0",
|
|
2165
|
+
fix: "Upgrade the project's @git.zone/tsdocker dev dependency to at least 3.3.0.",
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2080
2168
|
} else {
|
|
2081
2169
|
findings.push({
|
|
2082
2170
|
level: "error",
|
|
2083
|
-
message: "tsdocker command is not available",
|
|
2084
|
-
fix: "Install @git.zone/tsdocker
|
|
2171
|
+
message: "Project-local tsdocker command is not available",
|
|
2172
|
+
fix: "Install @git.zone/tsdocker as a project dev dependency.",
|
|
2085
2173
|
});
|
|
2086
2174
|
}
|
|
2087
2175
|
} catch (error) {
|
|
2088
2176
|
findings.push({
|
|
2089
2177
|
level: "error",
|
|
2090
|
-
message: "Could not execute tsdocker",
|
|
2178
|
+
message: "Could not execute project-local tsdocker",
|
|
2091
2179
|
fix: error instanceof Error ? error.message : String(error),
|
|
2092
2180
|
});
|
|
2093
2181
|
}
|
|
@@ -71,15 +71,14 @@ const dockerCommand = `timeout ${dockerTimeoutSeconds}s docker`;
|
|
|
71
71
|
export class DockerPruner {
|
|
72
72
|
private smartshell = new plugins.smartshell.Smartshell({ executor: 'bash' });
|
|
73
73
|
|
|
74
|
-
private async run(commandArg: string): Promise<
|
|
75
|
-
|
|
76
|
-
return { exitCode: result.exitCode, stdout: result.stdout || '' };
|
|
74
|
+
private async run(commandArg: string): Promise<plugins.smartshell.IExecResult> {
|
|
75
|
+
return this.smartshell.execSilent(commandArg);
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
private async listLines(commandArg: string): Promise<string[]> {
|
|
80
79
|
const result = await this.run(commandArg);
|
|
81
80
|
if (result.exitCode !== 0) {
|
|
82
|
-
throw new Error(result.
|
|
81
|
+
throw new Error(result.combinedOutput);
|
|
83
82
|
}
|
|
84
83
|
if (!result.stdout.trim()) {
|
|
85
84
|
return [];
|
|
@@ -99,7 +98,7 @@ export class DockerPruner {
|
|
|
99
98
|
`${dockerCommand} inspect ${idsArg.map((id) => shellQuote(id)).join(' ')}`,
|
|
100
99
|
);
|
|
101
100
|
if (result.exitCode !== 0) {
|
|
102
|
-
throw new Error(result.
|
|
101
|
+
throw new Error(result.combinedOutput);
|
|
103
102
|
}
|
|
104
103
|
try {
|
|
105
104
|
const parsed = JSON.parse(result.stdout);
|
|
@@ -287,7 +286,7 @@ export class DockerPruner {
|
|
|
287
286
|
}
|
|
288
287
|
const result = await this.run(`${dockerCommand} rm ${shellQuote(planned.id)}`);
|
|
289
288
|
if (result.exitCode !== 0) {
|
|
290
|
-
throw new Error(`Failed to remove container ${planned.name}: ${result.
|
|
289
|
+
throw new Error(`Failed to remove container ${planned.name}: ${result.combinedOutput}`);
|
|
291
290
|
}
|
|
292
291
|
removedContainers.push(planned.name);
|
|
293
292
|
logger.log('ok', `Removed container ${planned.name}`);
|
|
@@ -322,7 +321,7 @@ export class DockerPruner {
|
|
|
322
321
|
}
|
|
323
322
|
const result = await this.run(`${dockerCommand} volume rm ${shellQuote(planned.name)}`);
|
|
324
323
|
if (result.exitCode !== 0) {
|
|
325
|
-
throw new Error(`Failed to remove volume ${planned.name}: ${result.
|
|
324
|
+
throw new Error(`Failed to remove volume ${planned.name}: ${result.combinedOutput}`);
|
|
326
325
|
}
|
|
327
326
|
removedVolumes.push(planned.name);
|
|
328
327
|
logger.log('ok', `Removed volume ${planned.name}`);
|
package/ts/mod_release/index.ts
CHANGED
|
@@ -9,10 +9,13 @@ import {
|
|
|
9
9
|
readPendingChangelog,
|
|
10
10
|
} from "../helpers.changelog.js";
|
|
11
11
|
import {
|
|
12
|
+
getDeclaredTsdockerRange,
|
|
12
13
|
resolveReleaseWorkflow,
|
|
14
|
+
supportsBoundedBuildRegistryAuthentication,
|
|
13
15
|
type IResolvedReleaseWorkflow,
|
|
14
16
|
} from "../helpers.workflow.js";
|
|
15
17
|
import * as commitHelpers from "../mod_commit/mod.helpers.js";
|
|
18
|
+
import { hasInstalledProjectTsdockerBinary } from "../helpers.tsdocker.js";
|
|
16
19
|
|
|
17
20
|
type TTargetStatus = "success" | "already-published" | "skipped" | "failed";
|
|
18
21
|
|
|
@@ -247,7 +250,7 @@ async function runNpmTarget(
|
|
|
247
250
|
for (const registry of workflow.npmRegistries) {
|
|
248
251
|
const command = `pnpm publish --registry=${registry} --access=${workflow.npmAccessLevel}`;
|
|
249
252
|
const result = await smartshellInstance.exec(command);
|
|
250
|
-
const output =
|
|
253
|
+
const output = result.combinedOutput;
|
|
251
254
|
if (result.exitCode === 0) {
|
|
252
255
|
results.push({ target: registry, status: "success" });
|
|
253
256
|
} else if (isAlreadyPublishedOutput(output) && workflow.npmAlreadyPublished === "success") {
|
|
@@ -267,9 +270,46 @@ async function runDockerTarget(
|
|
|
267
270
|
return [{ target: "docker", status: "skipped", message: "disabled" }];
|
|
268
271
|
}
|
|
269
272
|
|
|
273
|
+
let packageJson: unknown;
|
|
274
|
+
try {
|
|
275
|
+
packageJson = JSON.parse(
|
|
276
|
+
await plugins.fs.readFile(plugins.path.join(paths.cwd, "package.json"), "utf8"),
|
|
277
|
+
);
|
|
278
|
+
} catch {
|
|
279
|
+
packageJson = undefined;
|
|
280
|
+
}
|
|
281
|
+
if (!getDeclaredTsdockerRange(packageJson)) {
|
|
282
|
+
return [{
|
|
283
|
+
target: "tsdocker",
|
|
284
|
+
status: "failed",
|
|
285
|
+
message: "Docker releases require @git.zone/tsdocker as a project dependency",
|
|
286
|
+
}];
|
|
287
|
+
}
|
|
288
|
+
if (!(await hasInstalledProjectTsdockerBinary(paths.cwd))) {
|
|
289
|
+
return [{
|
|
290
|
+
target: "tsdocker",
|
|
291
|
+
status: "failed",
|
|
292
|
+
message: "Docker releases require the installed project-local tSDocker binary",
|
|
293
|
+
}];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (workflow.dockerBuildRegistries.length > 0) {
|
|
297
|
+
const versionResult = await smartshellInstance.exec("pnpm exec tsdocker --version");
|
|
298
|
+
if (
|
|
299
|
+
versionResult.exitCode !== 0
|
|
300
|
+
|| !supportsBoundedBuildRegistryAuthentication(versionResult.combinedOutput)
|
|
301
|
+
) {
|
|
302
|
+
return [{
|
|
303
|
+
target: "tsdocker",
|
|
304
|
+
status: "failed",
|
|
305
|
+
message: "release.targets.docker.buildRegistries requires project-local @git.zone/tsdocker >= 3.3.0",
|
|
306
|
+
}];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
270
310
|
const command = buildTsdockerPushCommand(workflow);
|
|
271
311
|
const result = await smartshellInstance.exec(command);
|
|
272
|
-
const output =
|
|
312
|
+
const output = result.combinedOutput;
|
|
273
313
|
return [{
|
|
274
314
|
target: workflow.dockerPatterns.length > 0
|
|
275
315
|
? `tsdocker:${workflow.dockerPatterns.join(",")}`
|
|
@@ -279,8 +319,25 @@ async function runDockerTarget(
|
|
|
279
319
|
}];
|
|
280
320
|
}
|
|
281
321
|
|
|
282
|
-
|
|
283
|
-
|
|
322
|
+
type TDockerCommandWorkflow = Pick<
|
|
323
|
+
IResolvedReleaseWorkflow,
|
|
324
|
+
| "dockerRegistry"
|
|
325
|
+
| "dockerBuildRegistries"
|
|
326
|
+
| "dockerNoBuild"
|
|
327
|
+
| "dockerCached"
|
|
328
|
+
| "dockerParallel"
|
|
329
|
+
| "dockerContext"
|
|
330
|
+
| "dockerPatterns"
|
|
331
|
+
>;
|
|
332
|
+
|
|
333
|
+
export function buildTsdockerPushCommand(workflow: TDockerCommandWorkflow): string {
|
|
334
|
+
const commandParts = ["pnpm", "exec", "tsdocker", "push"];
|
|
335
|
+
if (workflow.dockerRegistry) {
|
|
336
|
+
commandParts.push(`--registry=${shellQuote(workflow.dockerRegistry)}`);
|
|
337
|
+
}
|
|
338
|
+
if (workflow.dockerBuildRegistries.length > 0) {
|
|
339
|
+
commandParts.push(`--build-registries=${shellQuote(workflow.dockerBuildRegistries.join(","))}`);
|
|
340
|
+
}
|
|
284
341
|
if (workflow.dockerNoBuild) {
|
|
285
342
|
commandParts.push("--no-build");
|
|
286
343
|
}
|
|
@@ -328,6 +385,8 @@ function printReleasePlan(workflow: IResolvedReleaseWorkflow): void {
|
|
|
328
385
|
}
|
|
329
386
|
if (workflow.targets.includes("docker")) {
|
|
330
387
|
console.log(`docker engine: ${workflow.dockerEngine}`);
|
|
388
|
+
console.log(`docker registry: ${workflow.dockerRegistry || "all configured registries"}`);
|
|
389
|
+
console.log(`docker build authentication: ${workflow.dockerBuildRegistries.length > 0 ? workflow.dockerBuildRegistries.join(", ") : "all configured registries"}`);
|
|
331
390
|
console.log(`docker patterns: ${workflow.dockerPatterns.length > 0 ? workflow.dockerPatterns.join(", ") : "all Dockerfiles"}`);
|
|
332
391
|
console.log(`docker options: ${formatDockerOptions(workflow)}`);
|
|
333
392
|
}
|
|
@@ -336,6 +395,10 @@ function printReleasePlan(workflow: IResolvedReleaseWorkflow): void {
|
|
|
336
395
|
|
|
337
396
|
function formatDockerOptions(workflow: IResolvedReleaseWorkflow): string {
|
|
338
397
|
const options: string[] = [];
|
|
398
|
+
if (workflow.dockerRegistry) options.push(`registry=${workflow.dockerRegistry}`);
|
|
399
|
+
if (workflow.dockerBuildRegistries.length > 0) {
|
|
400
|
+
options.push(`buildRegistries=${workflow.dockerBuildRegistries.join(",")}`);
|
|
401
|
+
}
|
|
339
402
|
if (workflow.dockerCached) options.push("cached");
|
|
340
403
|
if (workflow.dockerParallel) options.push(`parallel=${workflow.dockerParallel === true ? "true" : workflow.dockerParallel}`);
|
|
341
404
|
if (workflow.dockerNoBuild) options.push("no-build");
|