simplemdg-dev-cli 2.0.4 → 2.4.4

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.
Files changed (140) hide show
  1. package/README.md +63 -354
  2. package/USER_GUIDE.md +55 -378
  3. package/dist/commands/cds.command.js +69 -60
  4. package/dist/commands/cds.command.js.map +1 -1
  5. package/dist/commands/cf-db.command.d.ts +2 -0
  6. package/dist/commands/cf-db.command.js +606 -0
  7. package/dist/commands/cf-db.command.js.map +1 -0
  8. package/dist/commands/cf.command.js +291 -280
  9. package/dist/commands/cf.command.js.map +1 -1
  10. package/dist/commands/gitlab.command.d.ts +2 -0
  11. package/dist/commands/gitlab.command.js +351 -0
  12. package/dist/commands/gitlab.command.js.map +1 -0
  13. package/dist/commands/npmrc.command.js +50 -44
  14. package/dist/commands/npmrc.command.js.map +1 -1
  15. package/dist/core/cache.d.ts +1 -1
  16. package/dist/core/cache.js +58 -31
  17. package/dist/core/cache.js.map +1 -1
  18. package/dist/core/cds.js +32 -22
  19. package/dist/core/cds.js.map +1 -1
  20. package/dist/core/cf-env-parser.d.ts +1 -1
  21. package/dist/core/cf-env-parser.js +4 -1
  22. package/dist/core/cf-env-parser.js.map +1 -1
  23. package/dist/core/cf.d.ts +1 -1
  24. package/dist/core/cf.js +46 -31
  25. package/dist/core/cf.js.map +1 -1
  26. package/dist/core/db/db-btp.d.ts +48 -0
  27. package/dist/core/db/db-btp.js +162 -0
  28. package/dist/core/db/db-btp.js.map +1 -0
  29. package/dist/core/db/db-cache.d.ts +35 -0
  30. package/dist/core/db/db-cache.js +164 -0
  31. package/dist/core/db/db-cache.js.map +1 -0
  32. package/dist/core/db/db-connection.d.ts +22 -0
  33. package/dist/core/db/db-connection.js +73 -0
  34. package/dist/core/db/db-connection.js.map +1 -0
  35. package/dist/core/db/db-crypto.d.ts +3 -0
  36. package/dist/core/db/db-crypto.js +54 -0
  37. package/dist/core/db/db-crypto.js.map +1 -0
  38. package/dist/core/db/db-hana-adapter.d.ts +32 -0
  39. package/dist/core/db/db-hana-adapter.js +243 -0
  40. package/dist/core/db/db-hana-adapter.js.map +1 -0
  41. package/dist/core/db/db-metadata.d.ts +25 -0
  42. package/dist/core/db/db-metadata.js +150 -0
  43. package/dist/core/db/db-metadata.js.map +1 -0
  44. package/dist/core/db/db-postgres-adapter.d.ts +30 -0
  45. package/dist/core/db/db-postgres-adapter.js +245 -0
  46. package/dist/core/db/db-postgres-adapter.js.map +1 -0
  47. package/dist/core/db/db-query-files.d.ts +20 -0
  48. package/dist/core/db/db-query-files.js +106 -0
  49. package/dist/core/db/db-query-files.js.map +1 -0
  50. package/dist/core/db/db-query-history.d.ts +5 -0
  51. package/dist/core/db/db-query-history.js +49 -0
  52. package/dist/core/db/db-query-history.js.map +1 -0
  53. package/dist/core/db/db-row.d.ts +22 -0
  54. package/dist/core/db/db-row.js +70 -0
  55. package/dist/core/db/db-row.js.map +1 -0
  56. package/dist/core/db/db-studio-html.d.ts +4 -0
  57. package/dist/core/db/db-studio-html.js +437 -0
  58. package/dist/core/db/db-studio-html.js.map +1 -0
  59. package/dist/core/db/db-studio-server.d.ts +11 -0
  60. package/dist/core/db/db-studio-server.js +465 -0
  61. package/dist/core/db/db-studio-server.js.map +1 -0
  62. package/dist/core/db/db-types.d.ts +174 -0
  63. package/dist/core/db/db-types.js +3 -0
  64. package/dist/core/db/db-types.js.map +1 -0
  65. package/dist/core/db/db-vcap-parser.d.ts +7 -0
  66. package/dist/core/db/db-vcap-parser.js +137 -0
  67. package/dist/core/db/db-vcap-parser.js.map +1 -0
  68. package/dist/core/doctor.d.ts +1 -1
  69. package/dist/core/doctor.js +14 -8
  70. package/dist/core/doctor.js.map +1 -1
  71. package/dist/core/guide.js +31 -26
  72. package/dist/core/guide.js.map +1 -1
  73. package/dist/core/install.d.ts +1 -1
  74. package/dist/core/install.js +17 -11
  75. package/dist/core/install.js.map +1 -1
  76. package/dist/core/navigator.d.ts +17 -0
  77. package/dist/core/navigator.js +140 -0
  78. package/dist/core/navigator.js.map +1 -0
  79. package/dist/core/npmrc.js +29 -16
  80. package/dist/core/npmrc.js.map +1 -1
  81. package/dist/core/process.js +11 -6
  82. package/dist/core/process.js.map +1 -1
  83. package/dist/core/prompts.js +16 -8
  84. package/dist/core/prompts.js.map +1 -1
  85. package/dist/core/repository.d.ts +1 -1
  86. package/dist/core/repository.js +16 -9
  87. package/dist/core/repository.js.map +1 -1
  88. package/dist/core/scanner.d.ts +1 -1
  89. package/dist/core/scanner.js +13 -7
  90. package/dist/core/scanner.js.map +1 -1
  91. package/dist/core/tooling.d.ts +28 -0
  92. package/dist/core/tooling.js +168 -0
  93. package/dist/core/tooling.js.map +1 -0
  94. package/dist/core/types.js +2 -1
  95. package/dist/core/version-conflict.d.ts +2 -2
  96. package/dist/core/version-conflict.js +11 -6
  97. package/dist/core/version-conflict.js.map +1 -1
  98. package/dist/index.js +65 -48
  99. package/dist/index.js.map +1 -1
  100. package/dist/types-local.js +2 -1
  101. package/package.json +12 -6
  102. package/src/commands/cds.command.ts +529 -0
  103. package/src/commands/cf-db.command.ts +636 -0
  104. package/src/commands/cf.command.ts +3345 -0
  105. package/src/commands/gitlab.command.ts +373 -0
  106. package/src/commands/npmrc.command.ts +581 -0
  107. package/src/core/cache.ts +332 -0
  108. package/src/core/cds.ts +278 -0
  109. package/src/core/cf-env-parser.ts +131 -0
  110. package/src/core/cf.ts +271 -0
  111. package/src/core/db/db-btp.ts +207 -0
  112. package/src/core/db/db-cache.ts +215 -0
  113. package/src/core/db/db-connection.ts +79 -0
  114. package/src/core/db/db-crypto.ts +53 -0
  115. package/src/core/db/db-hana-adapter.ts +294 -0
  116. package/src/core/db/db-metadata.ts +174 -0
  117. package/src/core/db/db-postgres-adapter.ts +275 -0
  118. package/src/core/db/db-query-files.ts +130 -0
  119. package/src/core/db/db-query-history.ts +53 -0
  120. package/src/core/db/db-row.ts +93 -0
  121. package/src/core/db/db-studio-html.ts +439 -0
  122. package/src/core/db/db-studio-server.ts +559 -0
  123. package/src/core/db/db-types.ts +195 -0
  124. package/src/core/db/db-vcap-parser.ts +182 -0
  125. package/src/core/doctor.ts +70 -0
  126. package/src/core/guide.ts +261 -0
  127. package/src/core/install.ts +91 -0
  128. package/src/core/navigator.ts +164 -0
  129. package/src/core/npmrc.ts +171 -0
  130. package/src/core/process.ts +75 -0
  131. package/src/core/prompts.ts +225 -0
  132. package/src/core/repository.ts +36 -0
  133. package/src/core/scanner.ts +41 -0
  134. package/src/core/tooling.ts +207 -0
  135. package/src/core/types.ts +152 -0
  136. package/src/core/version-conflict.ts +46 -0
  137. package/src/index.ts +460 -0
  138. package/src/types/external.d.ts +3 -0
  139. package/src/types-local.ts +11 -0
  140. package/tsconfig.json +17 -0
@@ -0,0 +1,581 @@
1
+ import path from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import { Command } from "commander";
4
+ import prompts from "prompts";
5
+ import {
6
+ readCache,
7
+ rememberNpmrcHost,
8
+ rememberNpmrcOutputFileName,
9
+ rememberNpmrcPackages,
10
+ rememberNpmrcScope,
11
+ rememberNpmrcTokenEntry,
12
+ } from "../core/cache";
13
+ import {
14
+ normalizeGitLabHost,
15
+ normalizeNpmScope,
16
+ parsePackageInputList,
17
+ readPackageJsonName,
18
+ writeNpmrcFile,
19
+ } from "../core/npmrc";
20
+ import { searchableSelectChoice, searchableSelectOrInput } from "../core/prompts";
21
+ import { resolveRepositoryPath } from "../core/repository";
22
+ import type { TNpmrcPackageEntry, TNpmrcTokenEntry } from "../core/types";
23
+
24
+ const DEFAULT_PROJECT_NAME = "default";
25
+
26
+ const DEFAULT_HOST = "gitlab.simplemdg.com";
27
+ const DEFAULT_SCOPE = "@simplemdg";
28
+
29
+ type TNpmrcCreateOptions = {
30
+ cwd?: string;
31
+ scope?: string;
32
+ host?: string;
33
+ packageId?: string;
34
+ packageName?: string;
35
+ token?: string;
36
+ tokenLabel?: string;
37
+ out?: string;
38
+ noSaveToken?: boolean;
39
+ alwaysAuth?: boolean;
40
+ };
41
+
42
+ type TNpmrcImportOptions = {
43
+ cwd?: string;
44
+ project?: string;
45
+ scope?: string;
46
+ host?: string;
47
+ ids?: string;
48
+ file?: string;
49
+ };
50
+
51
+ type TNpmrcTokenOptions = {
52
+ scope?: string;
53
+ host?: string;
54
+ token?: string;
55
+ label?: string;
56
+ };
57
+
58
+ function validatePackageId(value: string): true | string {
59
+ return /^\d+$/.test(value.trim()) ? true : "Package ID must be a number";
60
+ }
61
+
62
+ function validateNotEmpty(label: string): (value: string) => true | string {
63
+ return (value: string) => value.trim() ? true : `${label} is required`;
64
+ }
65
+
66
+ function maskToken(token: string): string {
67
+ const trimmedToken = token.trim();
68
+
69
+ if (trimmedToken.length <= 8) {
70
+ return "********";
71
+ }
72
+
73
+ return `${trimmedToken.slice(0, 4)}...${trimmedToken.slice(-4)}`;
74
+ }
75
+
76
+ function buildPackageEntryTitle(entry: TNpmrcPackageEntry): string {
77
+ return `${entry.packageName} (${entry.packageId}) - ${entry.scope} @ ${entry.host}`;
78
+ }
79
+
80
+ function buildTokenTitle(entry: TNpmrcTokenEntry): string {
81
+ return `${entry.label} - ${entry.scope} @ ${entry.host} - ${maskToken(entry.token)}`;
82
+ }
83
+
84
+ function uniquePackageEntries(entries: TNpmrcPackageEntry[]): TNpmrcPackageEntry[] {
85
+ const result: TNpmrcPackageEntry[] = [];
86
+ const keys = new Set<string>();
87
+
88
+ for (const entry of entries) {
89
+ const key = `${entry.host}|${entry.scope}|${entry.packageId}`;
90
+
91
+ if (keys.has(key)) {
92
+ continue;
93
+ }
94
+
95
+ keys.add(key);
96
+ result.push(entry);
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ async function resolveCurrentProjectName(cwd: string, explicitProjectName?: string): Promise<string> {
103
+ if (explicitProjectName?.trim()) {
104
+ return explicitProjectName.trim();
105
+ }
106
+
107
+ const repositoryPath = await resolveRepositoryPath(cwd);
108
+ const packageJsonName = await readPackageJsonName(repositoryPath);
109
+
110
+ return packageJsonName ?? path.basename(repositoryPath) ?? DEFAULT_PROJECT_NAME;
111
+ }
112
+
113
+ async function askHost(providedHost?: string): Promise<string> {
114
+ if (providedHost?.trim()) {
115
+ return normalizeGitLabHost(providedHost);
116
+ }
117
+
118
+ const cache = await readCache();
119
+ return normalizeGitLabHost(await searchableSelectOrInput({
120
+ message: "GitLab host",
121
+ values: cache.npmrc.hosts,
122
+ initialValue: cache.npmrc.hosts[0] ?? DEFAULT_HOST,
123
+ validate: validateNotEmpty("Host"),
124
+ customValueTitle: (value) => `Use typed host: ${value}`,
125
+ }));
126
+ }
127
+
128
+ async function askScope(providedScope?: string): Promise<string> {
129
+ if (providedScope?.trim()) {
130
+ return normalizeNpmScope(providedScope);
131
+ }
132
+
133
+ const cache = await readCache();
134
+ return normalizeNpmScope(await searchableSelectOrInput({
135
+ message: "NPM scope",
136
+ values: cache.npmrc.scopes,
137
+ initialValue: cache.npmrc.scopes[0] ?? DEFAULT_SCOPE,
138
+ validate: validateNotEmpty("Scope"),
139
+ customValueTitle: (value) => `Use typed scope: ${value}`,
140
+ }));
141
+ }
142
+
143
+ async function askPackageEntry(options: {
144
+ projectName: string;
145
+ host: string;
146
+ scope: string;
147
+ providedPackageId?: string;
148
+ providedPackageName?: string;
149
+ }): Promise<TNpmrcPackageEntry> {
150
+ const now = new Date().toISOString();
151
+
152
+ if (options.providedPackageId?.trim()) {
153
+ const validationResult = validatePackageId(options.providedPackageId);
154
+
155
+ if (validationResult !== true) {
156
+ throw new Error(validationResult);
157
+ }
158
+
159
+ const packageName = options.providedPackageName?.trim() || options.providedPackageId.trim();
160
+
161
+ return {
162
+ packageId: options.providedPackageId.trim(),
163
+ packageName,
164
+ host: options.host,
165
+ scope: options.scope,
166
+ updatedAt: now,
167
+ };
168
+ }
169
+
170
+ const cache = await readCache();
171
+ const projectPackages = cache.npmrc.packageIdsByProject[options.projectName]?.packages ?? [];
172
+ const legacyProjectIds = cache.npmrc.packageIdsByProject[options.projectName]?.packageIds ?? [];
173
+ const legacyProjectPackages = legacyProjectIds.map<TNpmrcPackageEntry>((packageId) => ({
174
+ packageId,
175
+ packageName: packageId,
176
+ host: options.host,
177
+ scope: options.scope,
178
+ updatedAt: now,
179
+ }));
180
+ const globalPackages = cache.npmrc.packages ?? [];
181
+ const legacyGlobalPackages = cache.npmrc.packageIds.map<TNpmrcPackageEntry>((packageId) => ({
182
+ packageId,
183
+ packageName: packageId,
184
+ host: options.host,
185
+ scope: options.scope,
186
+ updatedAt: now,
187
+ }));
188
+ const packages = uniquePackageEntries([
189
+ ...projectPackages,
190
+ ...legacyProjectPackages,
191
+ ...globalPackages,
192
+ ...legacyGlobalPackages,
193
+ ]);
194
+
195
+ const selectedPackageId = await searchableSelectChoice({
196
+ message: `GitLab package for ${options.projectName}`,
197
+ choices: packages.map((entry) => ({
198
+ title: buildPackageEntryTitle(entry),
199
+ value: entry.packageId,
200
+ })),
201
+ validateCustomValue: validatePackageId,
202
+ customValueTitle: (value) => `Use typed package ID: ${value}`,
203
+ });
204
+
205
+ const cachedPackage = packages.find((entry) => entry.packageId === selectedPackageId);
206
+
207
+ if (cachedPackage) {
208
+ return {
209
+ ...cachedPackage,
210
+ host: cachedPackage.host || options.host,
211
+ scope: cachedPackage.scope || options.scope,
212
+ updatedAt: now,
213
+ };
214
+ }
215
+
216
+ const response = await prompts({
217
+ type: "text",
218
+ name: "packageName",
219
+ message: `Package name for ${selectedPackageId}`,
220
+ initial: options.providedPackageName ?? selectedPackageId,
221
+ validate: validateNotEmpty("Package name"),
222
+ });
223
+
224
+ if (!response.packageName) {
225
+ throw new Error("Package name is required");
226
+ }
227
+
228
+ return {
229
+ packageId: selectedPackageId,
230
+ packageName: String(response.packageName).trim(),
231
+ host: options.host,
232
+ scope: options.scope,
233
+ updatedAt: now,
234
+ };
235
+ }
236
+
237
+ async function askToken(options: {
238
+ host: string;
239
+ scope: string;
240
+ providedToken?: string;
241
+ providedLabel?: string;
242
+ }): Promise<TNpmrcTokenEntry> {
243
+ const now = new Date().toISOString();
244
+
245
+ if (options.providedToken?.trim()) {
246
+ return {
247
+ host: options.host,
248
+ scope: options.scope,
249
+ token: options.providedToken.trim(),
250
+ label: options.providedLabel?.trim() || `${options.scope} @ ${options.host}`,
251
+ updatedAt: now,
252
+ };
253
+ }
254
+
255
+ const cache = await readCache();
256
+ const scopedTokens = cache.npmrc.tokenEntries.filter((entry) => {
257
+ return entry.host === options.host && entry.scope === options.scope;
258
+ });
259
+ const allTokens = cache.npmrc.tokenEntries;
260
+ const legacyTokens = cache.npmrc.tokens.map<TNpmrcTokenEntry>((token, index) => ({
261
+ host: options.host,
262
+ scope: options.scope,
263
+ token,
264
+ label: `Legacy token ${index + 1}`,
265
+ updatedAt: now,
266
+ }));
267
+ const tokens = [...scopedTokens, ...allTokens, ...legacyTokens];
268
+
269
+ if (tokens.length > 0) {
270
+ const selectedToken = await searchableSelectChoice({
271
+ message: `GitLab npm auth token for ${options.scope} @ ${options.host}`,
272
+ choices: tokens.map((entry) => ({
273
+ title: buildTokenTitle(entry),
274
+ value: entry.token,
275
+ })),
276
+ validateCustomValue: validateNotEmpty("Token"),
277
+ customValueTitle: (value) => `Use typed token: ${maskToken(value)}`,
278
+ });
279
+ const cachedToken = tokens.find((entry) => entry.token === selectedToken);
280
+
281
+ if (cachedToken) {
282
+ return {
283
+ ...cachedToken,
284
+ host: options.host,
285
+ scope: options.scope,
286
+ updatedAt: now,
287
+ };
288
+ }
289
+
290
+ return {
291
+ host: options.host,
292
+ scope: options.scope,
293
+ token: selectedToken,
294
+ label: options.providedLabel?.trim() || `${options.scope} @ ${options.host}`,
295
+ updatedAt: now,
296
+ };
297
+ }
298
+
299
+ const response = await prompts({
300
+ type: "password",
301
+ name: "token",
302
+ message: `GitLab npm auth token for ${options.scope} @ ${options.host}`,
303
+ validate: (value: string) => value.trim() ? true : "Token is required",
304
+ });
305
+
306
+ if (!response.token) {
307
+ throw new Error("Token is required");
308
+ }
309
+
310
+ return {
311
+ host: options.host,
312
+ scope: options.scope,
313
+ token: String(response.token).trim(),
314
+ label: options.providedLabel?.trim() || `${options.scope} @ ${options.host}`,
315
+ updatedAt: now,
316
+ };
317
+ }
318
+
319
+ async function createNpmrc(options: TNpmrcCreateOptions): Promise<void> {
320
+ const cwd = options.cwd ?? process.cwd();
321
+ const projectName = await resolveCurrentProjectName(cwd);
322
+ const cache = await readCache();
323
+ const host = await askHost(options.host);
324
+ const scope = await askScope(options.scope);
325
+ const packageEntry = await askPackageEntry({
326
+ projectName,
327
+ host,
328
+ scope,
329
+ providedPackageId: options.packageId,
330
+ providedPackageName: options.packageName,
331
+ });
332
+ const tokenEntry = await askToken({
333
+ host,
334
+ scope,
335
+ providedToken: options.token,
336
+ providedLabel: options.tokenLabel,
337
+ });
338
+ const outputFileName = options.out ?? await searchableSelectOrInput({
339
+ message: "Output npmrc file",
340
+ values: cache.npmrc.outputFileNames,
341
+ initialValue: ".npmrc",
342
+ validate: validateNotEmpty("Output file"),
343
+ customValueTitle: (value) => `Use typed file name: ${value}`,
344
+ });
345
+
346
+ const outputPath = await writeNpmrcFile({
347
+ host,
348
+ scope,
349
+ packageId: packageEntry.packageId,
350
+ token: tokenEntry.token,
351
+ outputFileName,
352
+ alwaysAuth: options.alwaysAuth ?? true,
353
+ });
354
+
355
+ await rememberNpmrcHost(host);
356
+ await rememberNpmrcScope(scope);
357
+ await rememberNpmrcPackages(projectName, [packageEntry]);
358
+ await rememberNpmrcOutputFileName(outputFileName);
359
+
360
+ if (options.noSaveToken !== true) {
361
+ await rememberNpmrcTokenEntry(tokenEntry);
362
+ }
363
+
364
+ console.log(`Created ${outputPath}`);
365
+ console.log(`${scope}:registry=https://${host}/api/v4/projects/${packageEntry.packageId}/packages/npm/`);
366
+ console.log(`Package: ${packageEntry.packageName} (${packageEntry.packageId})`);
367
+ console.log(`Token: ${tokenEntry.label} (${maskToken(tokenEntry.token)})`);
368
+ }
369
+
370
+ async function importPackages(options: TNpmrcImportOptions): Promise<void> {
371
+ const projectName = await resolveCurrentProjectName(options.cwd ?? process.cwd(), options.project);
372
+ const host = await askHost(options.host);
373
+ const scope = await askScope(options.scope);
374
+ let rawInput = options.ids ?? "";
375
+
376
+ if (options.file?.trim()) {
377
+ const filePath = path.resolve(options.cwd ?? process.cwd(), options.file);
378
+ const fileContent = await readFile(filePath, "utf8");
379
+ rawInput = `${rawInput}\n${fileContent}`;
380
+ }
381
+
382
+ if (!rawInput.trim()) {
383
+ const response = await prompts({
384
+ type: "text",
385
+ name: "packages",
386
+ message: `Packages for ${projectName}. Use: name|id, name,id, or id only`,
387
+ validate: (value: string) => parsePackageInputList(value).length > 0 ? true : "Enter at least one package ID",
388
+ });
389
+
390
+ if (!response.packages) {
391
+ throw new Error("No packages entered");
392
+ }
393
+
394
+ rawInput = response.packages as string;
395
+ }
396
+
397
+ const parsedPackages = parsePackageInputList(rawInput);
398
+
399
+ if (parsedPackages.length === 0) {
400
+ throw new Error("No valid packages found. Package IDs must be numbers.");
401
+ }
402
+
403
+ const packageEntries = parsedPackages.map<TNpmrcPackageEntry>((entry) => ({
404
+ packageId: entry.packageId,
405
+ packageName: entry.packageName,
406
+ host,
407
+ scope,
408
+ updatedAt: new Date().toISOString(),
409
+ }));
410
+
411
+ await rememberNpmrcHost(host);
412
+ await rememberNpmrcScope(scope);
413
+ await rememberNpmrcPackages(projectName, packageEntries);
414
+
415
+ console.log(`Imported ${packageEntries.length} package(s) for ${projectName}:`);
416
+ for (const entry of packageEntries) {
417
+ console.log(`- ${entry.packageName} (${entry.packageId}) - ${entry.scope} @ ${entry.host}`);
418
+ }
419
+ }
420
+
421
+ async function updateToken(options: TNpmrcTokenOptions): Promise<void> {
422
+ const host = await askHost(options.host);
423
+ const scope = await askScope(options.scope);
424
+ const response = options.token?.trim()
425
+ ? { token: options.token.trim(), label: options.label?.trim() || `${scope} @ ${host}` }
426
+ : await prompts([
427
+ {
428
+ type: "password",
429
+ name: "token",
430
+ message: `New GitLab npm auth token for ${scope} @ ${host}`,
431
+ validate: (value: string) => value.trim() ? true : "Token is required",
432
+ },
433
+ {
434
+ type: "text",
435
+ name: "label",
436
+ message: "Token label",
437
+ initial: options.label?.trim() || `${scope} @ ${host}`,
438
+ validate: validateNotEmpty("Token label"),
439
+ },
440
+ ]);
441
+
442
+ if (!response.token) {
443
+ throw new Error("Token is required");
444
+ }
445
+
446
+ const tokenEntry: TNpmrcTokenEntry = {
447
+ host,
448
+ scope,
449
+ token: String(response.token).trim(),
450
+ label: String(response.label || `${scope} @ ${host}`).trim(),
451
+ updatedAt: new Date().toISOString(),
452
+ };
453
+
454
+ await rememberNpmrcHost(host);
455
+ await rememberNpmrcScope(scope);
456
+ await rememberNpmrcTokenEntry(tokenEntry);
457
+
458
+ console.log(`Updated token for ${scope} @ ${host}: ${tokenEntry.label} (${maskToken(tokenEntry.token)})`);
459
+ }
460
+
461
+ async function listNpmrcCache(): Promise<void> {
462
+ const cache = await readCache();
463
+
464
+ console.log("NPMRC cache");
465
+ console.log("");
466
+ console.log(`Hosts: ${cache.npmrc.hosts.join(", ") || "N/A"}`);
467
+ console.log(`Scopes: ${cache.npmrc.scopes.join(", ") || "N/A"}`);
468
+ console.log(`Output files: ${cache.npmrc.outputFileNames.join(", ") || "N/A"}`);
469
+ console.log("");
470
+ console.log("Tokens:");
471
+
472
+ if (cache.npmrc.tokenEntries.length === 0 && cache.npmrc.tokens.length === 0) {
473
+ console.log("- N/A");
474
+ } else {
475
+ for (const entry of cache.npmrc.tokenEntries) {
476
+ console.log(`- ${entry.label}: ${entry.scope} @ ${entry.host} - ${maskToken(entry.token)}`);
477
+ }
478
+
479
+ if (cache.npmrc.tokens.length > 0) {
480
+ console.log(`- Legacy tokens: ${cache.npmrc.tokens.length}`);
481
+ }
482
+ }
483
+
484
+ console.log("");
485
+ console.log("Global packages:");
486
+
487
+ if (cache.npmrc.packages.length === 0 && cache.npmrc.packageIds.length === 0) {
488
+ console.log("- N/A");
489
+ } else {
490
+ for (const entry of cache.npmrc.packages) {
491
+ console.log(`- ${entry.packageName} (${entry.packageId}) - ${entry.scope} @ ${entry.host}`);
492
+ }
493
+
494
+ for (const packageId of cache.npmrc.packageIds.filter((packageId) => {
495
+ return !cache.npmrc.packages.some((entry) => entry.packageId === packageId);
496
+ })) {
497
+ console.log(`- ${packageId} (${packageId})`);
498
+ }
499
+ }
500
+
501
+ console.log("");
502
+ console.log("Packages by project:");
503
+
504
+ const projects = Object.values(cache.npmrc.packageIdsByProject);
505
+
506
+ if (projects.length === 0) {
507
+ console.log("- N/A");
508
+ return;
509
+ }
510
+
511
+ for (const project of projects) {
512
+ console.log(`- ${project.projectName}:`);
513
+
514
+ if ((project.packages?.length ?? 0) === 0 && project.packageIds.length === 0) {
515
+ console.log(" - N/A");
516
+ continue;
517
+ }
518
+
519
+ for (const entry of project.packages ?? []) {
520
+ console.log(` - ${entry.packageName} (${entry.packageId}) - ${entry.scope} @ ${entry.host}`);
521
+ }
522
+
523
+ for (const packageId of project.packageIds.filter((packageId) => {
524
+ return !(project.packages ?? []).some((entry) => entry.packageId === packageId);
525
+ })) {
526
+ console.log(` - ${packageId} (${packageId})`);
527
+ }
528
+ }
529
+ }
530
+
531
+ export function registerNpmrcCommands(program: Command): void {
532
+ const npmrcCommand = program
533
+ .command("npmrc")
534
+ .description("Create and cache .npmrc config for GitLab npm package registry");
535
+
536
+ npmrcCommand
537
+ .command("create")
538
+ .alias("init")
539
+ .description("Create .npmrc for @simplemdg GitLab package registry")
540
+ .option("--cwd <path>", "Repository path", process.cwd())
541
+ .option("--scope <scope>", "NPM scope. Example: @simplemdg")
542
+ .option("--host <host>", "GitLab host")
543
+ .option("--package-id <packageId>", "GitLab project/package ID")
544
+ .option("--package-name <packageName>", "Human-readable package name for cache")
545
+ .option("--token <token>", "GitLab package registry token")
546
+ .option("--token-label <label>", "Human-readable token label")
547
+ .option("--out <fileName>", "Output file", ".npmrc")
548
+ .option("--no-save-token", "Do not cache token")
549
+ .option("--no-always-auth", "Write always-auth=false")
550
+ .action(createNpmrc);
551
+
552
+ npmrcCommand
553
+ .command("import")
554
+ .alias("add")
555
+ .description("Import many GitLab packages into cache with package names")
556
+ .option("--cwd <path>", "Repository path", process.cwd())
557
+ .option("--project <name>", "Project cache name")
558
+ .option("--scope <scope>", "NPM scope. Example: @simplemdg")
559
+ .option("--host <host>", "GitLab host")
560
+ .option("--ids <ids>", "Packages separated by line. Examples: name|123 or name,123 or 123")
561
+ .option("--file <path>", "Text file containing packages")
562
+ .action(importPackages);
563
+
564
+ npmrcCommand
565
+ .command("token")
566
+ .alias("update-token")
567
+ .description("Add or update cached token for a scope and host")
568
+ .option("--scope <scope>", "NPM scope. Example: @simplemdg")
569
+ .option("--host <host>", "GitLab host")
570
+ .option("--token <token>", "New token")
571
+ .option("--label <label>", "Token label")
572
+ .action(updateToken);
573
+
574
+ npmrcCommand
575
+ .command("list")
576
+ .description("List cached npmrc values with package names")
577
+ .action(listNpmrcCache);
578
+
579
+ npmrcCommand
580
+ .action(createNpmrc);
581
+ }