@gadmin2n/cli 0.0.91 → 0.0.93

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 (36) hide show
  1. package/actions/index.d.ts +1 -1
  2. package/actions/index.js +1 -1
  3. package/actions/self-update.action.d.ts +5 -0
  4. package/actions/self-update.action.js +93 -0
  5. package/actions/update.action.d.ts +2 -0
  6. package/actions/update.action.js +945 -6
  7. package/commands/command.loader.js +2 -2
  8. package/commands/{template.command.d.ts → self-update.command.d.ts} +1 -1
  9. package/commands/self-update.command.js +35 -0
  10. package/commands/update.command.js +25 -8
  11. package/lib/install-manager.d.ts +48 -0
  12. package/lib/install-manager.js +125 -0
  13. package/lib/template-merge/config-loader.d.ts +15 -0
  14. package/lib/template-merge/config-loader.js +38 -0
  15. package/lib/template-merge/env-merger.d.ts +5 -0
  16. package/lib/template-merge/env-merger.js +57 -0
  17. package/lib/template-merge/glob.d.ts +5 -0
  18. package/lib/template-merge/glob.js +78 -0
  19. package/lib/template-merge/index.d.ts +8 -0
  20. package/lib/template-merge/index.js +24 -0
  21. package/lib/template-merge/json-merger.d.ts +5 -0
  22. package/lib/template-merge/json-merger.js +269 -0
  23. package/lib/template-merge/merger.d.ts +30 -0
  24. package/lib/template-merge/merger.js +2 -0
  25. package/lib/template-merge/prisma-merger.d.ts +5 -0
  26. package/lib/template-merge/prisma-merger.js +112 -0
  27. package/lib/template-merge/registry.d.ts +25 -0
  28. package/lib/template-merge/registry.js +42 -0
  29. package/lib/template-merge/ts-module-merger.d.ts +16 -0
  30. package/lib/template-merge/ts-module-merger.js +193 -0
  31. package/lib/version-check.d.ts +39 -0
  32. package/lib/version-check.js +157 -0
  33. package/package.json +2 -2
  34. package/actions/template.action.d.ts +0 -7
  35. package/actions/template.action.js +0 -570
  36. package/commands/template.command.js +0 -42
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TsModuleMerger = void 0;
4
+ const ts = require("typescript");
5
+ const schematics_1 = require("@gadmin2n/schematics");
6
+ const METADATA_KEYS = ['imports', 'providers', 'controllers', 'exports'];
7
+ /**
8
+ * Smart merger for NestJS-style `*.module.ts` files.
9
+ *
10
+ * Strategy: extract symbols from template's `@Module(...)` metadata arrays
11
+ * (imports/providers/controllers/exports) and missing top-level `import { ... } from '...'`
12
+ * lines, then insert each missing item into the instance file. Existing items are
13
+ * preserved. Method bodies and other class members are not touched.
14
+ *
15
+ * Falls back (returns `{ok: false}`) when the instance has no `@Module` decorator
16
+ * or when AST parsing/insertion throws.
17
+ */
18
+ class TsModuleMerger {
19
+ constructor() {
20
+ this.name = 'ts-module';
21
+ }
22
+ merge(ctx) {
23
+ var _a, _b;
24
+ const policies = (_a = ctx.policies) !== null && _a !== void 0 ? _a : {};
25
+ const notes = [];
26
+ try {
27
+ const templateSource = ts.createSourceFile('template.ts', ctx.templateContent, ts.ScriptTarget.ES2017, true);
28
+ const instanceSourceInitial = ts.createSourceFile('instance.ts', ctx.instanceContent, ts.ScriptTarget.ES2017, true);
29
+ if (!findModuleDecoratorArg(instanceSourceInitial)) {
30
+ return { ok: false, reason: 'no @Module decorator in instance' };
31
+ }
32
+ const templateModuleArg = findModuleDecoratorArg(templateSource);
33
+ let content = ctx.instanceContent;
34
+ // 1. Per-slot metadata merge (imports, providers, controllers, exports).
35
+ if (templateModuleArg) {
36
+ for (const key of METADATA_KEYS) {
37
+ const policy = (_b = policies[`ts-module.${key}`]) !== null && _b !== void 0 ? _b : 'union';
38
+ if (policy === 'skip') {
39
+ notes.push(`${key}: skip (policy)`);
40
+ continue;
41
+ }
42
+ const templateElements = extractMetadataArrayElements(templateModuleArg, key, templateSource);
43
+ if (templateElements.length === 0)
44
+ continue;
45
+ for (const element of templateElements) {
46
+ // Re-parse the current (possibly mutated) instance to get fresh
47
+ // existing-element lookup. This mirrors the pattern used in
48
+ // schematics' service.factory: each MetadataManager.insert call
49
+ // takes the latest content.
50
+ const reparsed = ts.createSourceFile('instance.ts', content, ts.ScriptTarget.ES2017, true);
51
+ const arg = findModuleDecoratorArg(reparsed);
52
+ if (!arg) {
53
+ return { ok: false, reason: 'lost @Module decorator during merge' };
54
+ }
55
+ const existing = extractMetadataArrayElements(arg, key, reparsed);
56
+ if (existing.includes(element)) {
57
+ notes.push(`${key}: kept ${element}`);
58
+ continue;
59
+ }
60
+ const next = new schematics_1.MetadataManager(content).insert(key, element);
61
+ if (next === content) {
62
+ // MetadataManager returned content unchanged (e.g. symbol already
63
+ // exists by its own equality check). Treat as kept.
64
+ notes.push(`${key}: kept ${element}`);
65
+ }
66
+ else {
67
+ content = next;
68
+ notes.push(`${key}: added ${element}`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ // 2. Top-level import lines merge.
74
+ const templateImports = collectTopImports(templateSource);
75
+ const instanceForImports = ts.createSourceFile('instance.ts', content, ts.ScriptTarget.ES2017, true);
76
+ const instanceImports = collectTopImports(instanceForImports);
77
+ const existingSymbols = new Set();
78
+ for (const imp of instanceImports) {
79
+ for (const s of imp.symbols)
80
+ existingSymbols.add(s);
81
+ }
82
+ const linesToAdd = [];
83
+ for (const imp of templateImports) {
84
+ if (imp.symbols.length === 0)
85
+ continue;
86
+ const allExist = imp.symbols.every((s) => existingSymbols.has(s));
87
+ if (allExist)
88
+ continue;
89
+ linesToAdd.push(imp.line);
90
+ for (const s of imp.symbols)
91
+ existingSymbols.add(s);
92
+ notes.push(`import: added ${imp.symbols.join(', ')} from '${imp.from}'`);
93
+ }
94
+ if (linesToAdd.length > 0) {
95
+ let lastEnd = -1;
96
+ for (const stmt of instanceForImports.statements) {
97
+ if (ts.isImportDeclaration(stmt)) {
98
+ lastEnd = stmt.getEnd();
99
+ }
100
+ }
101
+ const insertion = '\n' + linesToAdd.join('\n');
102
+ if (lastEnd >= 0) {
103
+ content = content.slice(0, lastEnd) + insertion + content.slice(lastEnd);
104
+ }
105
+ else {
106
+ // No existing imports — prepend.
107
+ content = linesToAdd.join('\n') + '\n' + content;
108
+ }
109
+ }
110
+ if (content === ctx.instanceContent) {
111
+ return { ok: true, merged: content, notes: ['no changes'] };
112
+ }
113
+ return { ok: true, merged: content, notes };
114
+ }
115
+ catch (err) {
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ return { ok: false, reason: `ts-module merge failed: ${message}` };
118
+ }
119
+ }
120
+ }
121
+ exports.TsModuleMerger = TsModuleMerger;
122
+ /**
123
+ * Walk the AST to find the first `@Module(...)` decorator's
124
+ * `ObjectLiteralExpression` argument. Returns `undefined` if not found.
125
+ */
126
+ function findModuleDecoratorArg(source) {
127
+ let result;
128
+ const visit = (node) => {
129
+ if (result)
130
+ return;
131
+ if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {
132
+ const callee = node.expression.expression;
133
+ if (ts.isIdentifier(callee) && callee.text === 'Module') {
134
+ const arg = node.expression.arguments[0];
135
+ if (arg && ts.isObjectLiteralExpression(arg)) {
136
+ result = arg;
137
+ return;
138
+ }
139
+ }
140
+ }
141
+ ts.forEachChild(node, visit);
142
+ };
143
+ visit(source);
144
+ return result;
145
+ }
146
+ /**
147
+ * Extract the source text of each element of a metadata array property
148
+ * (e.g. `imports: [A, B.forRoot({...})]`).
149
+ */
150
+ function extractMetadataArrayElements(arg, key, source) {
151
+ for (const prop of arg.properties) {
152
+ if (!ts.isPropertyAssignment(prop))
153
+ continue;
154
+ let propName = '';
155
+ if (ts.isIdentifier(prop.name))
156
+ propName = prop.name.text;
157
+ else if (ts.isStringLiteral(prop.name))
158
+ propName = prop.name.text;
159
+ if (propName !== key)
160
+ continue;
161
+ if (!ts.isArrayLiteralExpression(prop.initializer))
162
+ continue;
163
+ return prop.initializer.elements.map((el) => el.getText(source));
164
+ }
165
+ return [];
166
+ }
167
+ /**
168
+ * Collect top-level `import { Named, ... } from '...'` declarations from a
169
+ * source file. Default and namespace imports are intentionally ignored — the
170
+ * NestJS module template convention is named-imports only.
171
+ */
172
+ function collectTopImports(source) {
173
+ const result = [];
174
+ for (const stmt of source.statements) {
175
+ if (!ts.isImportDeclaration(stmt))
176
+ continue;
177
+ if (!ts.isStringLiteral(stmt.moduleSpecifier))
178
+ continue;
179
+ const symbols = [];
180
+ const ic = stmt.importClause;
181
+ if (ic && ic.namedBindings && ts.isNamedImports(ic.namedBindings)) {
182
+ for (const el of ic.namedBindings.elements) {
183
+ symbols.push(el.name.text);
184
+ }
185
+ }
186
+ result.push({
187
+ line: stmt.getText(source),
188
+ symbols,
189
+ from: stmt.moduleSpecifier.text,
190
+ });
191
+ }
192
+ return result;
193
+ }
@@ -0,0 +1,39 @@
1
+ export interface InstalledInfo {
2
+ name: string;
3
+ version: string;
4
+ }
5
+ export interface VersionInfo {
6
+ packageName: string;
7
+ installed: string;
8
+ latest: string;
9
+ }
10
+ /**
11
+ * Read the CLI's own installed version by walking up from __dirname looking
12
+ * for the closest package.json. Returns null if not found.
13
+ */
14
+ export declare function getInstalledVersion(): InstalledInfo | null;
15
+ /**
16
+ * HTTPS GET https://registry.npmjs.org/<name>/latest with a hard timeout.
17
+ * Returns the latest version string on success, or null on any failure
18
+ * (network error, non-200, parse error, timeout). Never throws.
19
+ */
20
+ export declare function fetchLatestVersion(packageName: string, timeoutMs?: number): Promise<string | null>;
21
+ /**
22
+ * Numeric compare of dotted version strings. Strips any prerelease suffix
23
+ * (`-alpha.1`, `-rc.0`, etc.) before comparing. Returns positive if a > b,
24
+ * negative if a < b, 0 if equal.
25
+ */
26
+ export declare function compareVersions(a: string, b: string): number;
27
+ /**
28
+ * Convenience wrapper: read installed version + fetch latest. Returns
29
+ * null on any failure (no installed pkg.json located, network failure,
30
+ * timeout). Never throws.
31
+ */
32
+ export declare function checkVersion(timeoutMs?: number): Promise<VersionInfo | null>;
33
+ /**
34
+ * Print a yellow warning banner explaining that the CLI is outdated and
35
+ * how to upgrade. Uses the auto-detected install manager to suggest the
36
+ * matching upgrade command (npm install -g / yarn global add / pnpm add -g).
37
+ * Non-blocking — caller decides whether to continue.
38
+ */
39
+ export declare function printOutdatedWarning(info: VersionInfo): void;
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.printOutdatedWarning = exports.checkVersion = exports.compareVersions = exports.fetchLatestVersion = exports.getInstalledVersion = void 0;
13
+ const chalk = require("chalk");
14
+ const fs = require("fs");
15
+ const https = require("https");
16
+ const path = require("path");
17
+ const install_manager_1 = require("./install-manager");
18
+ const REGISTRY_URL = 'https://registry.npmjs.org';
19
+ /**
20
+ * Read the CLI's own installed version by walking up from __dirname looking
21
+ * for the closest package.json. Returns null if not found.
22
+ */
23
+ function getInstalledVersion() {
24
+ let dir = __dirname;
25
+ for (let i = 0; i < 8; i++) {
26
+ const pkgPath = path.join(dir, 'package.json');
27
+ if (fs.existsSync(pkgPath)) {
28
+ try {
29
+ const json = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
30
+ if (json.name && json.version) {
31
+ return { name: json.name, version: json.version };
32
+ }
33
+ }
34
+ catch (_a) {
35
+ // ignore and keep walking
36
+ }
37
+ }
38
+ const parent = path.dirname(dir);
39
+ if (parent === dir)
40
+ break;
41
+ dir = parent;
42
+ }
43
+ return null;
44
+ }
45
+ exports.getInstalledVersion = getInstalledVersion;
46
+ /**
47
+ * HTTPS GET https://registry.npmjs.org/<name>/latest with a hard timeout.
48
+ * Returns the latest version string on success, or null on any failure
49
+ * (network error, non-200, parse error, timeout). Never throws.
50
+ */
51
+ function fetchLatestVersion(packageName, timeoutMs = 1500) {
52
+ return new Promise((resolve) => {
53
+ const url = `${REGISTRY_URL}/${encodeURIComponent(packageName).replace('%40', '@')}/latest`;
54
+ let settled = false;
55
+ const finish = (v) => {
56
+ if (settled)
57
+ return;
58
+ settled = true;
59
+ resolve(v);
60
+ };
61
+ const req = https.get(url, { timeout: timeoutMs }, (res) => {
62
+ if (res.statusCode !== 200) {
63
+ res.resume();
64
+ finish(null);
65
+ return;
66
+ }
67
+ const chunks = [];
68
+ res.on('data', (c) => chunks.push(c));
69
+ res.on('end', () => {
70
+ try {
71
+ const json = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
72
+ finish(typeof json.version === 'string' ? json.version : null);
73
+ }
74
+ catch (_a) {
75
+ finish(null);
76
+ }
77
+ });
78
+ res.on('error', () => finish(null));
79
+ });
80
+ req.on('error', () => finish(null));
81
+ req.on('timeout', () => {
82
+ req.destroy();
83
+ finish(null);
84
+ });
85
+ });
86
+ }
87
+ exports.fetchLatestVersion = fetchLatestVersion;
88
+ /**
89
+ * Numeric compare of dotted version strings. Strips any prerelease suffix
90
+ * (`-alpha.1`, `-rc.0`, etc.) before comparing. Returns positive if a > b,
91
+ * negative if a < b, 0 if equal.
92
+ */
93
+ function compareVersions(a, b) {
94
+ const parse = (v) => v.split('.').map((n) => parseInt(n.split('-')[0], 10) || 0);
95
+ const pa = parse(a);
96
+ const pb = parse(b);
97
+ const len = Math.max(pa.length, pb.length);
98
+ for (let i = 0; i < len; i++) {
99
+ const da = pa[i] || 0;
100
+ const db = pb[i] || 0;
101
+ if (da !== db)
102
+ return da - db;
103
+ }
104
+ return 0;
105
+ }
106
+ exports.compareVersions = compareVersions;
107
+ /**
108
+ * Convenience wrapper: read installed version + fetch latest. Returns
109
+ * null on any failure (no installed pkg.json located, network failure,
110
+ * timeout). Never throws.
111
+ */
112
+ function checkVersion(timeoutMs = 1500) {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ const installed = getInstalledVersion();
115
+ if (!installed)
116
+ return null;
117
+ const latest = yield fetchLatestVersion(installed.name, timeoutMs);
118
+ if (!latest)
119
+ return null;
120
+ return {
121
+ packageName: installed.name,
122
+ installed: installed.version,
123
+ latest,
124
+ };
125
+ });
126
+ }
127
+ exports.checkVersion = checkVersion;
128
+ /**
129
+ * Print a yellow warning banner explaining that the CLI is outdated and
130
+ * how to upgrade. Uses the auto-detected install manager to suggest the
131
+ * matching upgrade command (npm install -g / yarn global add / pnpm add -g).
132
+ * Non-blocking — caller decides whether to continue.
133
+ */
134
+ function printOutdatedWarning(info) {
135
+ const detection = (0, install_manager_1.detectInstallManager)();
136
+ const upgradeCmd = (0, install_manager_1.buildUpgradeCommand)(detection.manager, info.packageName, 'latest');
137
+ const bar = '═══════════════════════════════════════════════════';
138
+ console.warn();
139
+ console.warn(chalk.yellow.bold(bar));
140
+ console.warn(chalk.yellow.bold('⚠️ CLI is outdated:') +
141
+ chalk.yellow(` installed ${info.installed}, latest ${info.latest}`));
142
+ console.warn(chalk.yellow(' Templates ship bundled with the CLI — using an outdated CLI'));
143
+ console.warn(chalk.yellow(' means updating against an outdated template.'));
144
+ console.warn();
145
+ console.warn(chalk.yellow(' Upgrade with:'));
146
+ console.warn(chalk.cyan(` gadmin2 self-update`));
147
+ console.warn(chalk.gray(` (or: ${upgradeCmd})`));
148
+ console.warn(chalk.gray(` detected manager: ${detection.manager}` +
149
+ (detection.pathHint !== detection.manager
150
+ ? ` (path hint: ${detection.pathHint})`
151
+ : '')));
152
+ console.warn();
153
+ console.warn(chalk.gray(' To skip this check: gadmin2 update --no-version-check'));
154
+ console.warn(chalk.yellow.bold(bar));
155
+ console.warn();
156
+ }
157
+ exports.printOutdatedWarning = printOutdatedWarning;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gadmin2n/cli",
3
- "version": "0.0.91",
3
+ "version": "0.0.93",
4
4
  "description": "Gadmin - modern, fast, powerful node.js web framework (@cli)",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -47,7 +47,7 @@
47
47
  "@angular-devkit/core": "13.3.2",
48
48
  "@angular-devkit/schematics": "13.3.2",
49
49
  "@angular-devkit/schematics-cli": "13.3.2",
50
- "@gadmin2n/schematics": "^0.0.72",
50
+ "@gadmin2n/schematics": "^0.0.75",
51
51
  "abc": "^0.6.1",
52
52
  "chalk": "3.0.0",
53
53
  "chokidar": "3.5.3",
@@ -1,7 +0,0 @@
1
- import { Input } from '../commands';
2
- import { AbstractAction } from './abstract.action';
3
- export declare class TemplateAction extends AbstractAction {
4
- handle(inputs: Input[], options: Input[]): Promise<void>;
5
- private handleDiff;
6
- private handleUpdate;
7
- }