@git.zone/cli 2.24.0 → 2.25.1
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/gitzone.cli.js +11 -2
- package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +14 -1
- package/dist_ts/mod_services/classes.serviceconfiguration.js +32 -2
- package/dist_ts/mod_services/classes.servicemanager.d.ts +9 -0
- package/dist_ts/mod_services/classes.servicemanager.js +20 -4
- package/dist_ts/mod_services/classes.serviceoptions.d.ts +58 -0
- package/dist_ts/mod_services/classes.serviceoptions.js +93 -0
- package/dist_ts/mod_tools/classes.packagemanager.d.ts +13 -0
- package/dist_ts/mod_tools/classes.packagemanager.js +42 -1
- package/dist_ts/mod_tools/index.js +9 -2
- package/package.json +1 -1
- package/readme.hints.md +17 -0
- package/readme.md +52 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/gitzone.cli.ts +10 -0
- package/ts/mod_services/classes.serviceconfiguration.ts +48 -6
- package/ts/mod_services/classes.servicemanager.ts +32 -3
- package/ts/mod_services/classes.serviceoptions.ts +135 -0
- package/ts/mod_tools/classes.packagemanager.ts +54 -0
- package/ts/mod_tools/index.ts +11 -1
package/readme.md
CHANGED
|
@@ -527,6 +527,58 @@ non-local `MONGODB_HOST` is refused outright. Transactions continue to work, and
|
|
|
527
527
|
created without it bootstraps the configured root user through MongoDB's
|
|
528
528
|
localhost exception.
|
|
529
529
|
|
|
530
|
+
The setting is recorded in `.smartconfig.json`, so it is committed and a fresh
|
|
531
|
+
clone or CI run reproduces it without any manual step:
|
|
532
|
+
|
|
533
|
+
```json
|
|
534
|
+
{
|
|
535
|
+
"@git.zone/cli": {
|
|
536
|
+
"services": ["mongodb"],
|
|
537
|
+
"serviceOptions": {
|
|
538
|
+
"mongodb": { "auth": false }
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
`serviceOptions` is a **sibling** of `services`, never a richer `services`
|
|
545
|
+
value. `services` must stay a flat array of canonical lowercase strings because
|
|
546
|
+
`@git.zone/tsdeploy` derives a workload's `requiredCapabilities` from it and
|
|
547
|
+
rejects any other shape.
|
|
548
|
+
|
|
549
|
+
A committed declaration takes precedence over `.nogit/env.json`, so a stale
|
|
550
|
+
local file cannot silently diverge from what the repository declares. When
|
|
551
|
+
nothing is declared, an existing local value is preserved. When neither exists,
|
|
552
|
+
authentication is enabled. Because a declaration affects everyone who clones the
|
|
553
|
+
repository, `services status` states where the setting came from:
|
|
554
|
+
|
|
555
|
+
```text
|
|
556
|
+
⚠️ Auth: DISABLED (loopback only), declared in .smartconfig.json (applies to every checkout)
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
An older CLI that predates `serviceOptions` ignores the key and starts MongoDB
|
|
560
|
+
with authentication enabled — it degrades to the secure default, never the
|
|
561
|
+
insecure one.
|
|
562
|
+
|
|
563
|
+
### Checking which version is running
|
|
564
|
+
|
|
565
|
+
`gitzone --version` prints the bare version on the first line, followed by the
|
|
566
|
+
path it resolved from. A stale copy in a legacy pnpm global root can otherwise
|
|
567
|
+
make it look like an older version is installed when it is not:
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
gitzone --version
|
|
571
|
+
# 2.25.0
|
|
572
|
+
# resolved from: /home/you/.local/share/pnpm/store/v11/links/@git.zone/cli/2.25.0/…
|
|
573
|
+
|
|
574
|
+
gitzone --version --json
|
|
575
|
+
# {"version":"2.25.0","resolvedFrom":"…"}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
`gitzone tools update` also removes inert copies of managed packages left behind
|
|
579
|
+
in legacy global roots, provided the active root already supplies them and no
|
|
580
|
+
command shim still points there.
|
|
581
|
+
|
|
530
582
|
## Templates
|
|
531
583
|
|
|
532
584
|
Start new projects with built-in scaffolds:
|
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: '2.
|
|
6
|
+
version: '2.25.1',
|
|
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
|
}
|
package/ts/gitzone.cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getProcessUserArgv,
|
|
5
5
|
getRawCliMode,
|
|
6
6
|
parseCliArgv,
|
|
7
|
+
printJson,
|
|
7
8
|
} from "./helpers.climode.js";
|
|
8
9
|
import { commitinfo } from "./00_commitinfo_data.js";
|
|
9
10
|
|
|
@@ -130,7 +131,16 @@ export let run = async () => {
|
|
|
130
131
|
}
|
|
131
132
|
const argvArg = parseCliArgv(getProcessUserArgv());
|
|
132
133
|
if (argvArg.v || argvArg.version) {
|
|
134
|
+
if (rawCliMode.output === "json") {
|
|
135
|
+
printJson({ version: packageVersion, resolvedFrom: paths.packageDir });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// The bare version stays on its own line so `gitzone --version` remains
|
|
139
|
+
// parseable by scripts. The resolved path is added underneath because a
|
|
140
|
+
// stale copy in a legacy pnpm global root can otherwise make people
|
|
141
|
+
// conclude they are running a version they are not.
|
|
133
142
|
console.log(packageVersion);
|
|
143
|
+
console.log(`resolved from: ${paths.packageDir}`);
|
|
134
144
|
return;
|
|
135
145
|
}
|
|
136
146
|
await runParsedCommand(argvArg);
|
|
@@ -3,6 +3,12 @@ import * as helpers from './helpers.js';
|
|
|
3
3
|
import { logger } from '../gitzone.logging.js';
|
|
4
4
|
import { DockerContainer } from './classes.dockercontainer.js';
|
|
5
5
|
import { getServiceDataDirectory } from './classes.servicedatamarker.js';
|
|
6
|
+
import {
|
|
7
|
+
readServiceOptions,
|
|
8
|
+
resolveMongoAuth,
|
|
9
|
+
writeMongodbAuthOption,
|
|
10
|
+
type TServiceOptionSource,
|
|
11
|
+
} from './classes.serviceoptions.js';
|
|
6
12
|
|
|
7
13
|
/** Hosts that are considered local for the purposes of the no-auth guard. */
|
|
8
14
|
const localMongoHosts = ['localhost', '127.0.0.1', '::1'];
|
|
@@ -44,30 +50,60 @@ export class ServiceConfiguration {
|
|
|
44
50
|
private configPath: string;
|
|
45
51
|
private config!: IServiceConfig;
|
|
46
52
|
private docker: DockerContainer;
|
|
47
|
-
|
|
53
|
+
/** Where the effective MongoDB auth mode came from. */
|
|
54
|
+
private mongoAuthSource: TServiceOptionSource = 'default';
|
|
55
|
+
|
|
48
56
|
constructor() {
|
|
49
57
|
this.configPath = plugins.path.join(process.cwd(), '.nogit', 'env.json');
|
|
50
58
|
this.docker = new DockerContainer();
|
|
51
59
|
}
|
|
52
|
-
|
|
60
|
+
|
|
53
61
|
/**
|
|
54
62
|
* Load or create the configuration
|
|
55
63
|
*/
|
|
56
64
|
public async loadOrCreate(): Promise<IServiceConfig> {
|
|
57
65
|
await this.ensureNogitDirectory();
|
|
58
|
-
|
|
66
|
+
|
|
59
67
|
if (await this.configExists()) {
|
|
60
68
|
await this.loadConfig();
|
|
61
69
|
await this.updateMissingFields();
|
|
62
70
|
} else {
|
|
63
71
|
await this.createDefaultConfig();
|
|
64
72
|
}
|
|
65
|
-
|
|
73
|
+
|
|
74
|
+
// A committed declaration in .smartconfig.json overrides the local runtime
|
|
75
|
+
// value, so a fresh checkout reproduces the declared setup.
|
|
76
|
+
await this.applyDeclaredServiceOptions();
|
|
77
|
+
|
|
66
78
|
// Sync ports from existing Docker containers if they exist
|
|
67
79
|
await this.syncPortsFromDocker();
|
|
68
|
-
|
|
80
|
+
|
|
69
81
|
return this.config;
|
|
70
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fold `@git.zone/cli.serviceOptions` into the runtime configuration.
|
|
86
|
+
*/
|
|
87
|
+
private async applyDeclaredServiceOptions(): Promise<void> {
|
|
88
|
+
const declaredOptions = await readServiceOptions(process.cwd());
|
|
89
|
+
const resolved = resolveMongoAuth(declaredOptions, this.config.MONGODB_AUTH_ENABLED);
|
|
90
|
+
this.mongoAuthSource = resolved.source;
|
|
91
|
+
|
|
92
|
+
if (this.config.MONGODB_AUTH_ENABLED !== resolved.authEnabled) {
|
|
93
|
+
this.config.MONGODB_AUTH_ENABLED = resolved.authEnabled;
|
|
94
|
+
this.updateDerivedFields();
|
|
95
|
+
await this.saveConfig();
|
|
96
|
+
logger.log(
|
|
97
|
+
'note',
|
|
98
|
+
`📍 MongoDB auth ${resolved.authEnabled ? 'enabled' : 'disabled'} by .smartconfig.json declaration`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Where the effective MongoDB auth mode came from. */
|
|
104
|
+
public getMongoAuthSource(): TServiceOptionSource {
|
|
105
|
+
return this.mongoAuthSource;
|
|
106
|
+
}
|
|
71
107
|
|
|
72
108
|
/**
|
|
73
109
|
* Get the current configuration
|
|
@@ -128,13 +164,19 @@ export class ServiceConfiguration {
|
|
|
128
164
|
}
|
|
129
165
|
|
|
130
166
|
/**
|
|
131
|
-
* Persist the MongoDB auth mode
|
|
167
|
+
* Persist the MongoDB auth mode.
|
|
168
|
+
*
|
|
169
|
+
* Written to both `.smartconfig.json` (the committed declaration, so a fresh
|
|
170
|
+
* checkout or CI reproduces it) and `.nogit/env.json` (the resolved runtime
|
|
171
|
+
* value consumed by tooling). Writing both keeps them from drifting.
|
|
132
172
|
*/
|
|
133
173
|
public async setMongoAuthEnabled(enabledArg: boolean): Promise<void> {
|
|
134
174
|
this.config.MONGODB_AUTH_ENABLED = enabledArg;
|
|
135
175
|
this.updateDerivedFields();
|
|
136
176
|
this.assertMongoExposureIsSafe();
|
|
137
177
|
await this.saveConfig();
|
|
178
|
+
await writeMongodbAuthOption(enabledArg, process.cwd());
|
|
179
|
+
this.mongoAuthSource = enabledArg ? 'default' : 'declared';
|
|
138
180
|
}
|
|
139
181
|
|
|
140
182
|
/**
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from './classes.servicedatamarker.js';
|
|
12
12
|
import { logger } from '../gitzone.logging.js';
|
|
13
13
|
import type { ContainerStatus } from './classes.dockercontainer.js';
|
|
14
|
+
import type { TServiceOptionSource } from './classes.serviceoptions.js';
|
|
14
15
|
|
|
15
16
|
export interface IServiceStatus {
|
|
16
17
|
service: TServiceName;
|
|
@@ -25,6 +26,8 @@ export interface IServiceStatus {
|
|
|
25
26
|
portAvailable: boolean | null;
|
|
26
27
|
/** MongoDB only: whether the instance enforces authentication. */
|
|
27
28
|
authEnabled?: boolean;
|
|
29
|
+
/** MongoDB only: whether the auth mode is declared in committed config. */
|
|
30
|
+
authSource?: TServiceOptionSource;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export interface IServicesStatus {
|
|
@@ -73,7 +76,10 @@ export class ServiceManager {
|
|
|
73
76
|
// the misconfiguration. It is enforced where it matters, on the path that
|
|
74
77
|
// actually starts an unauthenticated database.
|
|
75
78
|
if (!this.config.isMongoAuthEnabled()) {
|
|
76
|
-
logger.log(
|
|
79
|
+
logger.log(
|
|
80
|
+
'note',
|
|
81
|
+
`⚠️ MongoDB auth is disabled for this project (loopback only) — ${this.describeMongoAuthSource()}`,
|
|
82
|
+
);
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
// Load service selection from .smartconfig.json
|
|
@@ -88,6 +94,22 @@ export class ServiceManager {
|
|
|
88
94
|
await this.globalRegistry.touchProject(process.cwd());
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Human phrasing for where the auth mode came from. A declared auth-off is
|
|
99
|
+
* committed to the repository and therefore affects everyone who clones it,
|
|
100
|
+
* so it must never read the same as a local-only choice.
|
|
101
|
+
*/
|
|
102
|
+
private describeMongoAuthSource(): string {
|
|
103
|
+
switch (this.config.getMongoAuthSource()) {
|
|
104
|
+
case 'declared':
|
|
105
|
+
return 'declared in .smartconfig.json (applies to every checkout)';
|
|
106
|
+
case 'local':
|
|
107
|
+
return 'set locally in .nogit/env.json';
|
|
108
|
+
default:
|
|
109
|
+
return 'default';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
/**
|
|
92
114
|
* Expose the resolved service configuration to the command layer.
|
|
93
115
|
*/
|
|
@@ -332,7 +354,10 @@ export class ServiceManager {
|
|
|
332
354
|
logger.log('info', ` Container: ${containers.mongo}`);
|
|
333
355
|
logger.log('info', ` Port: ${config.MONGODB_PORT}`);
|
|
334
356
|
if (!this.config.isMongoAuthEnabled()) {
|
|
335
|
-
logger.log(
|
|
357
|
+
logger.log(
|
|
358
|
+
'note',
|
|
359
|
+
` ⚠️ Auth: DISABLED — published on loopback only (127.0.0.1), ${this.describeMongoAuthSource()}`,
|
|
360
|
+
);
|
|
336
361
|
}
|
|
337
362
|
logger.log('info', ` Connection: ${this.config.getMongoConnectionString()}`);
|
|
338
363
|
|
|
@@ -866,6 +891,7 @@ export class ServiceManager {
|
|
|
866
891
|
this.config.getMongoConnectionString(),
|
|
867
892
|
);
|
|
868
893
|
mongo.authEnabled = this.config.isMongoAuthEnabled();
|
|
894
|
+
mongo.authSource = this.config.getMongoAuthSource();
|
|
869
895
|
|
|
870
896
|
const minio = await describe(
|
|
871
897
|
'minio',
|
|
@@ -913,7 +939,10 @@ export class ServiceManager {
|
|
|
913
939
|
logger.log('info', ` ├─ Container: ${containers.mongo}`);
|
|
914
940
|
logger.log('info', ` ├─ Port: ${config.MONGODB_PORT}`);
|
|
915
941
|
if (!this.config.isMongoAuthEnabled()) {
|
|
916
|
-
logger.log(
|
|
942
|
+
logger.log(
|
|
943
|
+
'note',
|
|
944
|
+
` ├─ ⚠️ Auth: DISABLED (loopback only), ${this.describeMongoAuthSource()}`,
|
|
945
|
+
);
|
|
917
946
|
}
|
|
918
947
|
logger.log('info', ` ├─ Connection: ${this.config.getMongoConnectionString()}`);
|
|
919
948
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as plugins from './mod.plugins.js';
|
|
2
|
+
import {
|
|
3
|
+
getCliConfigValueFromData,
|
|
4
|
+
readSmartconfigFile,
|
|
5
|
+
setCliConfigValueInData,
|
|
6
|
+
writeSmartconfigFile,
|
|
7
|
+
} from '../helpers.smartconfig.js';
|
|
8
|
+
import type { TServiceName } from './classes.servicedatamarker.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Committed, per-service configuration.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately a SIBLING of `@git.zone/cli.services` rather than a richer
|
|
14
|
+
* `services` value. `@git.zone/tsdeploy` derives a workload's
|
|
15
|
+
* `requiredCapabilities` from that array and **throws** on any element that is
|
|
16
|
+
* not a canonical lowercase string, so an array of objects — or a mixed array —
|
|
17
|
+
* would hard-fail real deployments. `services` therefore stays exactly
|
|
18
|
+
* `string[]`, and configuration lives here where TsDeploy never looks.
|
|
19
|
+
*
|
|
20
|
+
* Config path: `@git.zone/cli.serviceOptions`.
|
|
21
|
+
*/
|
|
22
|
+
export const serviceOptionsConfigKey = 'serviceOptions';
|
|
23
|
+
|
|
24
|
+
export interface IMongodbServiceOptions {
|
|
25
|
+
/**
|
|
26
|
+
* When false, mongod runs without authentication and is published on
|
|
27
|
+
* loopback only. Opt-in, for runtimes whose `node:crypto` cannot complete a
|
|
28
|
+
* SCRAM handshake (notably Deno). Absent means enabled.
|
|
29
|
+
*/
|
|
30
|
+
auth?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IServiceOptions {
|
|
34
|
+
mongodb?: IMongodbServiceOptions;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isPlainObject = (value: unknown): value is Record<string, any> => {
|
|
38
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Read declared service options from `.smartconfig.json`.
|
|
43
|
+
*
|
|
44
|
+
* Unknown or malformed values are ignored rather than throwing: this file is
|
|
45
|
+
* hand-edited, and a typo must not make every services command unusable. The
|
|
46
|
+
* effect of ignoring is always the safe default (auth enabled).
|
|
47
|
+
*/
|
|
48
|
+
export const readServiceOptions = async (
|
|
49
|
+
cwd: string = process.cwd(),
|
|
50
|
+
): Promise<IServiceOptions> => {
|
|
51
|
+
const smartconfigData = await readSmartconfigFile(cwd);
|
|
52
|
+
const rawOptions = getCliConfigValueFromData(smartconfigData, serviceOptionsConfigKey);
|
|
53
|
+
if (!isPlainObject(rawOptions)) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const options: IServiceOptions = {};
|
|
58
|
+
if (isPlainObject(rawOptions.mongodb) && typeof rawOptions.mongodb.auth === 'boolean') {
|
|
59
|
+
options.mongodb = { auth: rawOptions.mongodb.auth };
|
|
60
|
+
}
|
|
61
|
+
return options;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Declare whether MongoDB enforces authentication for this project.
|
|
66
|
+
*
|
|
67
|
+
* Writing `true` removes the declaration instead of recording it: the default
|
|
68
|
+
* is already "enabled", and an absent key keeps committed config free of noise
|
|
69
|
+
* while remaining unambiguous to an older CLI that ignores this key entirely.
|
|
70
|
+
*/
|
|
71
|
+
export const writeMongodbAuthOption = async (
|
|
72
|
+
authEnabledArg: boolean,
|
|
73
|
+
cwd: string = process.cwd(),
|
|
74
|
+
): Promise<void> => {
|
|
75
|
+
const smartconfigData = await readSmartconfigFile(cwd);
|
|
76
|
+
const rawOptions = getCliConfigValueFromData(smartconfigData, serviceOptionsConfigKey);
|
|
77
|
+
const nextOptions: Record<string, any> = isPlainObject(rawOptions) ? { ...rawOptions } : {};
|
|
78
|
+
|
|
79
|
+
if (authEnabledArg) {
|
|
80
|
+
if (isPlainObject(nextOptions.mongodb)) {
|
|
81
|
+
const { auth, ...restOfMongodb } = nextOptions.mongodb;
|
|
82
|
+
if (Object.keys(restOfMongodb).length > 0) {
|
|
83
|
+
nextOptions.mongodb = restOfMongodb;
|
|
84
|
+
} else {
|
|
85
|
+
delete nextOptions.mongodb;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
nextOptions.mongodb = { ...(isPlainObject(nextOptions.mongodb) ? nextOptions.mongodb : {}), auth: false };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (Object.keys(nextOptions).length === 0) {
|
|
93
|
+
// Drop the key entirely rather than leaving an empty object behind.
|
|
94
|
+
const cliConfig = smartconfigData['@git.zone/cli'];
|
|
95
|
+
if (isPlainObject(cliConfig)) {
|
|
96
|
+
delete cliConfig[serviceOptionsConfigKey];
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
setCliConfigValueInData(smartconfigData, serviceOptionsConfigKey, nextOptions);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await writeSmartconfigFile(smartconfigData, cwd);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Where an effective setting came from, so the CLI can say so out loud.
|
|
107
|
+
*/
|
|
108
|
+
export type TServiceOptionSource = 'declared' | 'local' | 'default';
|
|
109
|
+
|
|
110
|
+
export interface IResolvedMongoAuth {
|
|
111
|
+
authEnabled: boolean;
|
|
112
|
+
source: TServiceOptionSource;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the effective MongoDB auth mode.
|
|
117
|
+
*
|
|
118
|
+
* A committed declaration wins over `.nogit/env.json`. That ordering is what
|
|
119
|
+
* makes a fresh checkout reproducible: without it, a stale local env.json would
|
|
120
|
+
* silently diverge from what the repository declares. When nothing is declared,
|
|
121
|
+
* an existing local value is preserved so projects configured before
|
|
122
|
+
* `serviceOptions` existed keep working.
|
|
123
|
+
*/
|
|
124
|
+
export const resolveMongoAuth = (
|
|
125
|
+
declaredOptionsArg: IServiceOptions,
|
|
126
|
+
localValueArg: boolean | undefined,
|
|
127
|
+
): IResolvedMongoAuth => {
|
|
128
|
+
if (typeof declaredOptionsArg.mongodb?.auth === 'boolean') {
|
|
129
|
+
return { authEnabled: declaredOptionsArg.mongodb.auth, source: 'declared' };
|
|
130
|
+
}
|
|
131
|
+
if (typeof localValueArg === 'boolean') {
|
|
132
|
+
return { authEnabled: localValueArg, source: 'local' };
|
|
133
|
+
}
|
|
134
|
+
return { authEnabled: true, source: 'default' };
|
|
135
|
+
};
|
|
@@ -35,6 +35,11 @@ export interface ILegacyCleanupResult {
|
|
|
35
35
|
globalDir: string;
|
|
36
36
|
deleted: boolean;
|
|
37
37
|
reason?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Managed packages removed from a legacy root that itself had to be kept.
|
|
40
|
+
* Each entry is `name@version` of an inert copy that nothing resolves to.
|
|
41
|
+
*/
|
|
42
|
+
stalePackagesRemoved?: string[];
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export interface IShimSyncResult {
|
|
@@ -179,9 +184,18 @@ export class PackageManagerUtil {
|
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
if (!legacyRoot.safeToDelete) {
|
|
187
|
+
// The root must stay, but the managed copies inside it are inert: the
|
|
188
|
+
// current global root already provides those packages and no shim
|
|
189
|
+
// resolves here. Leaving them behind makes the directory look like the
|
|
190
|
+
// installed version, which misleads anyone inspecting it.
|
|
191
|
+
const stalePackagesRemoved = await this.removeStaleManagedPackages(
|
|
192
|
+
legacyRoot,
|
|
193
|
+
currentPackageNames,
|
|
194
|
+
);
|
|
182
195
|
cleanupResults.push({
|
|
183
196
|
globalDir: legacyRoot.globalDir,
|
|
184
197
|
deleted: false,
|
|
198
|
+
stalePackagesRemoved,
|
|
185
199
|
reason:
|
|
186
200
|
legacyRoot.unmanagedPackageNames.length > 0
|
|
187
201
|
? `kept because it also contains ${legacyRoot.unmanagedPackageNames.join(", ")}`
|
|
@@ -231,6 +245,46 @@ export class PackageManagerUtil {
|
|
|
231
245
|
return cleanupResults;
|
|
232
246
|
}
|
|
233
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Remove managed package copies from a legacy global root that has to be kept.
|
|
250
|
+
*
|
|
251
|
+
* Only removes a package when the active global root already provides it and
|
|
252
|
+
* no command shim points into this legacy root, so nothing that currently
|
|
253
|
+
* resolves can be affected. Fails closed: any doubt leaves the copy in place.
|
|
254
|
+
*/
|
|
255
|
+
private async removeStaleManagedPackages(
|
|
256
|
+
legacyRootArg: ILegacyGlobalRootInfo,
|
|
257
|
+
currentPackageNamesArg: Set<string>,
|
|
258
|
+
): Promise<string[]> {
|
|
259
|
+
const shimReferences = await this.getShimReferences(legacyRootArg.globalDir);
|
|
260
|
+
if (shimReferences === null || shimReferences.length > 0) {
|
|
261
|
+
// PNPM_HOME unknown, or something still resolves here.
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const removed: string[] = [];
|
|
266
|
+
for (const packageInfo of legacyRootArg.packages) {
|
|
267
|
+
if (!packageInfo.packagePath) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!currentPackageNamesArg.has(packageInfo.name)) {
|
|
271
|
+
// Not provided by the active root — removing it would lose the package.
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
await plugins.fs.rm(packageInfo.packagePath, {
|
|
276
|
+
recursive: true,
|
|
277
|
+
force: true,
|
|
278
|
+
});
|
|
279
|
+
removed.push(`${packageInfo.name}@${packageInfo.version}`);
|
|
280
|
+
} catch {
|
|
281
|
+
// Leave it rather than half-removing it.
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return removed;
|
|
286
|
+
}
|
|
287
|
+
|
|
234
288
|
public async syncCurrentGlobalShims(): Promise<IShimSyncResult[]> {
|
|
235
289
|
const pnpmShimDirs = await this.getPnpmShimDirs();
|
|
236
290
|
if (!pnpmShimDirs) {
|
package/ts/mod_tools/index.ts
CHANGED
|
@@ -95,7 +95,12 @@ async function runUpdate(argvArg: any, mode: ICliMode): Promise<void> {
|
|
|
95
95
|
const legacyCleanupRoots = legacyRoots.filter(
|
|
96
96
|
(legacyRoot) => legacyRoot.safeToDelete,
|
|
97
97
|
);
|
|
98
|
-
|
|
98
|
+
// A root that must be kept can still hold inert copies of managed packages.
|
|
99
|
+
// Gating cleanup on wholesale deletability alone left those copies in place
|
|
100
|
+
// forever, so the directory kept advertising a stale version.
|
|
101
|
+
const legacyCleanupNeeded =
|
|
102
|
+
legacyCleanupRoots.length > 0 ||
|
|
103
|
+
legacyRoots.some((legacyRoot) => !legacyRoot.safeToDelete && legacyRoot.packages.length > 0);
|
|
99
104
|
|
|
100
105
|
if (managedInstalledPackages.length === 0) {
|
|
101
106
|
console.log("No managed @git.zone packages found installed globally.");
|
|
@@ -485,6 +490,11 @@ async function cleanupLegacyInstalls(
|
|
|
485
490
|
console.log(
|
|
486
491
|
` ${cleanupResult.globalDir} kept (${cleanupResult.reason || "unknown reason"})`,
|
|
487
492
|
);
|
|
493
|
+
for (const stalePackage of cleanupResult.stalePackagesRemoved || []) {
|
|
494
|
+
console.log(
|
|
495
|
+
` removed stale inert copy ${stalePackage} (the active global root provides it)`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
488
498
|
}
|
|
489
499
|
}
|
|
490
500
|
console.log("");
|