slicejs-cli 3.2.0 → 3.4.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/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- package/.github/pull_request_template.md +22 -0
- package/CODE_OF_CONDUCT.md +126 -0
- package/ECOSYSTEM.md +9 -0
- package/LICENSE +21 -0
- package/README.md +171 -375
- package/client.js +664 -563
- package/commands/Print.js +167 -167
- package/commands/Validations.js +103 -103
- package/commands/build/build.js +40 -40
- package/commands/buildProduction/buildProduction.js +579 -579
- package/commands/bundle/bundle.js +235 -235
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +126 -126
- package/commands/deleteComponent/deleteComponent.js +77 -77
- package/commands/doctor/doctor.js +369 -369
- package/commands/getComponent/getComponent.js +747 -747
- package/commands/init/init.js +265 -261
- package/commands/listComponents/listComponents.js +175 -175
- package/commands/startServer/startServer.js +264 -264
- package/commands/startServer/watchServer.js +79 -79
- package/commands/types/types.js +16 -9
- package/commands/utils/LocalCliDelegation.js +53 -53
- package/commands/utils/PathHelper.js +68 -68
- package/commands/utils/VersionChecker.js +167 -167
- package/commands/utils/bundling/BundleGenerator.js +2292 -2292
- package/commands/utils/bundling/DependencyAnalyzer.js +933 -933
- package/commands/utils/updateManager.js +453 -453
- package/package.json +46 -46
- package/post.js +66 -25
- package/tests/bundle-generator.test.js +708 -708
- package/tests/bundle-v2-register-output.test.js +470 -470
- package/tests/client-launcher-contract.test.js +211 -211
- package/tests/client-update-flow-contract.test.js +272 -272
- package/tests/dependency-analyzer.test.js +24 -24
- package/tests/local-cli-delegation.test.js +79 -79
- package/tests/postinstall-command.test.js +72 -0
- package/tests/update-manager-notifications.test.js +88 -88
- package/refactor.md +0 -271
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
findNearestLocalCliEntry,
|
|
8
|
-
resolveLocalCliCandidate,
|
|
9
|
-
shouldDelegateToLocalCli,
|
|
10
|
-
isLocalDelegationDisabled
|
|
11
|
-
} from '../commands/utils/LocalCliDelegation.js';
|
|
12
|
-
|
|
13
|
-
test('isLocalDelegationDisabled returns true when env flag is set', () => {
|
|
14
|
-
const env = { SLICE_NO_LOCAL_DELEGATION: '1' };
|
|
15
|
-
assert.equal(isLocalDelegationDisabled(env), true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('isLocalDelegationDisabled returns false when env flag is missing', () => {
|
|
19
|
-
const env = {};
|
|
20
|
-
assert.equal(isLocalDelegationDisabled(env), false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('shouldDelegateToLocalCli is false when candidate is null', () => {
|
|
24
|
-
assert.equal(shouldDelegateToLocalCli('/tmp/current/client.js', null), false);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('shouldDelegateToLocalCli is false when candidate realpath equals current realpath', () => {
|
|
28
|
-
const same = '/tmp/current/client.js';
|
|
29
|
-
assert.equal(shouldDelegateToLocalCli(same, same), false);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('shouldDelegateToLocalCli is true when candidate differs from current', () => {
|
|
33
|
-
const current = '/tmp/global/client.js';
|
|
34
|
-
const local = '/tmp/project/node_modules/slicejs-cli/client.js';
|
|
35
|
-
assert.equal(shouldDelegateToLocalCli(current, local), true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('findNearestLocalCliEntry returns null when no candidate resolver hits', () => {
|
|
39
|
-
const cwd = path.join('/tmp', 'slice-nonexistent-project');
|
|
40
|
-
const resolver = () => null;
|
|
41
|
-
const result = findNearestLocalCliEntry(cwd, resolver);
|
|
42
|
-
assert.equal(result, null);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('findNearestLocalCliEntry returns first match while traversing upward', () => {
|
|
46
|
-
const cwd = '/repo/apps/web/src';
|
|
47
|
-
const calls = [];
|
|
48
|
-
const resolver = (dir) => {
|
|
49
|
-
calls.push(dir);
|
|
50
|
-
if (dir === '/repo/apps/web') {
|
|
51
|
-
return '/repo/apps/web/node_modules/slicejs-cli/client.js';
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const result = findNearestLocalCliEntry(cwd, resolver);
|
|
57
|
-
|
|
58
|
-
assert.equal(result, '/repo/apps/web/node_modules/slicejs-cli/client.js');
|
|
59
|
-
assert.deepEqual(calls, ['/repo/apps/web/src', '/repo/apps/web']);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('findNearestLocalCliEntry returns null when resolver is not a function', () => {
|
|
63
|
-
const result = findNearestLocalCliEntry('/repo/apps/web/src', null);
|
|
64
|
-
assert.equal(result, null);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('resolveLocalCliCandidate returns null when candidate path is a directory', () => {
|
|
68
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-cli-delegation-'));
|
|
69
|
-
const candidateDirectory = path.join(tempRoot, 'node_modules', 'slicejs-cli', 'client.js');
|
|
70
|
-
|
|
71
|
-
fs.mkdirSync(candidateDirectory, { recursive: true });
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const result = resolveLocalCliCandidate(tempRoot);
|
|
75
|
-
assert.equal(result, null);
|
|
76
|
-
} finally {
|
|
77
|
-
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
findNearestLocalCliEntry,
|
|
8
|
+
resolveLocalCliCandidate,
|
|
9
|
+
shouldDelegateToLocalCli,
|
|
10
|
+
isLocalDelegationDisabled
|
|
11
|
+
} from '../commands/utils/LocalCliDelegation.js';
|
|
12
|
+
|
|
13
|
+
test('isLocalDelegationDisabled returns true when env flag is set', () => {
|
|
14
|
+
const env = { SLICE_NO_LOCAL_DELEGATION: '1' };
|
|
15
|
+
assert.equal(isLocalDelegationDisabled(env), true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('isLocalDelegationDisabled returns false when env flag is missing', () => {
|
|
19
|
+
const env = {};
|
|
20
|
+
assert.equal(isLocalDelegationDisabled(env), false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('shouldDelegateToLocalCli is false when candidate is null', () => {
|
|
24
|
+
assert.equal(shouldDelegateToLocalCli('/tmp/current/client.js', null), false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('shouldDelegateToLocalCli is false when candidate realpath equals current realpath', () => {
|
|
28
|
+
const same = '/tmp/current/client.js';
|
|
29
|
+
assert.equal(shouldDelegateToLocalCli(same, same), false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('shouldDelegateToLocalCli is true when candidate differs from current', () => {
|
|
33
|
+
const current = '/tmp/global/client.js';
|
|
34
|
+
const local = '/tmp/project/node_modules/slicejs-cli/client.js';
|
|
35
|
+
assert.equal(shouldDelegateToLocalCli(current, local), true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('findNearestLocalCliEntry returns null when no candidate resolver hits', () => {
|
|
39
|
+
const cwd = path.join('/tmp', 'slice-nonexistent-project');
|
|
40
|
+
const resolver = () => null;
|
|
41
|
+
const result = findNearestLocalCliEntry(cwd, resolver);
|
|
42
|
+
assert.equal(result, null);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('findNearestLocalCliEntry returns first match while traversing upward', () => {
|
|
46
|
+
const cwd = '/repo/apps/web/src';
|
|
47
|
+
const calls = [];
|
|
48
|
+
const resolver = (dir) => {
|
|
49
|
+
calls.push(dir);
|
|
50
|
+
if (dir === '/repo/apps/web') {
|
|
51
|
+
return '/repo/apps/web/node_modules/slicejs-cli/client.js';
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const result = findNearestLocalCliEntry(cwd, resolver);
|
|
57
|
+
|
|
58
|
+
assert.equal(result, '/repo/apps/web/node_modules/slicejs-cli/client.js');
|
|
59
|
+
assert.deepEqual(calls, ['/repo/apps/web/src', '/repo/apps/web']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('findNearestLocalCliEntry returns null when resolver is not a function', () => {
|
|
63
|
+
const result = findNearestLocalCliEntry('/repo/apps/web/src', null);
|
|
64
|
+
assert.equal(result, null);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('resolveLocalCliCandidate returns null when candidate path is a directory', () => {
|
|
68
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-cli-delegation-'));
|
|
69
|
+
const candidateDirectory = path.join(tempRoot, 'node_modules', 'slicejs-cli', 'client.js');
|
|
70
|
+
|
|
71
|
+
fs.mkdirSync(candidateDirectory, { recursive: true });
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = resolveLocalCliCandidate(tempRoot);
|
|
75
|
+
assert.equal(result, null);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const clientPath = path.join(__dirname, '..', 'client.js');
|
|
9
|
+
const source = fs.readFileSync(clientPath, 'utf-8');
|
|
10
|
+
|
|
11
|
+
test('postinstall command is registered in client.js', () => {
|
|
12
|
+
const hasSetupCommand = source.includes('.command("postinstall")');
|
|
13
|
+
assert.ok(hasSetupCommand, 'client.js must register a "postinstall" command');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('postinstall command has a description', () => {
|
|
17
|
+
const match = source.match(/\.command\(["']postinstall["']\)\s*\.description\(["']([^"']+)["']\)/);
|
|
18
|
+
assert.ok(match, 'postinstall command must have a .description() call');
|
|
19
|
+
assert.ok(match[1].length > 0, 'postinstall command description must not be empty');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('postinstall command checks npm_config_global environment variable', () => {
|
|
23
|
+
const hasGlobalCheck = source.includes('npm_config_global');
|
|
24
|
+
assert.ok(hasGlobalCheck, 'postinstall command must check npm_config_global to detect global installation');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('postinstall command prints global install warning', () => {
|
|
28
|
+
const hasGlobalWarning = source.includes('Global installation');
|
|
29
|
+
assert.ok(hasGlobalWarning, 'postinstall command must warn about global installation');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('postinstall command prints local install success message', () => {
|
|
33
|
+
const hasSuccessMsg = source.includes('installed successfully');
|
|
34
|
+
assert.ok(hasSuccessMsg, 'postinstall command must show success message for local installs');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('postinstall command uses getProjectRoot from PathHelper', () => {
|
|
38
|
+
const hasPathHelper = source.includes("getProjectRoot(import.meta.url)");
|
|
39
|
+
assert.ok(hasPathHelper, 'postinstall command must use getProjectRoot resolver');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('postinstall command writes npm scripts to package.json via fs', () => {
|
|
43
|
+
const hasFsWrite = source.includes('writeFileSync(pkgPath');
|
|
44
|
+
assert.ok(hasFsWrite, 'postinstall command must write scripts to package.json');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('postinstall command writes slice:dev script to package.json', () => {
|
|
48
|
+
const hasDevScript = source.includes("'slice:dev': 'slice dev'");
|
|
49
|
+
assert.ok(hasDevScript, 'postinstall command must add "slice:dev" script to package.json');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('postinstall command writes all slice:* scripts to package.json', () => {
|
|
53
|
+
const scripts = [
|
|
54
|
+
'slice:dev', 'slice:start', 'slice:create', 'slice:list',
|
|
55
|
+
'slice:delete', 'slice:init', 'slice:get', 'slice:browse',
|
|
56
|
+
'slice:sync', 'slice:version', 'slice:update'
|
|
57
|
+
];
|
|
58
|
+
for (const script of scripts) {
|
|
59
|
+
assert.ok(source.includes(script), `postinstall command must configure ${script} script`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('postinstall command action is a function', () => {
|
|
64
|
+
const SetupActionPattern = /\.command\(["']postinstall["']\)[\s\S]*?\.action\(\(\)\s*=>\s*\{[\s\S]*?\}\)/;
|
|
65
|
+
const match = source.match(SetupActionPattern);
|
|
66
|
+
assert.ok(match, 'postinstall command must have an .action() with arrow function');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('postinstall command provides npm uninstall -g instruction for global installs', () => {
|
|
70
|
+
const hasUninstallInstruction = source.includes('npm uninstall -g slicejs-cli');
|
|
71
|
+
assert.ok(hasUninstallInstruction, 'postinstall command must tell users how to uninstall global CLI');
|
|
72
|
+
});
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import Print from '../commands/Print.js';
|
|
4
|
-
import { UpdateManager } from '../commands/utils/updateManager.js';
|
|
5
|
-
|
|
6
|
-
test('notifyAvailableUpdates shows advisory output and never invokes interactive update flow', async () => {
|
|
7
|
-
const manager = new UpdateManager();
|
|
8
|
-
const calls = {
|
|
9
|
-
display: 0,
|
|
10
|
-
prompted: 0,
|
|
11
|
-
info: [],
|
|
12
|
-
error: []
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
manager.checkForUpdates = async () => ({
|
|
16
|
-
hasUpdates: true,
|
|
17
|
-
updates: [
|
|
18
|
-
{
|
|
19
|
-
name: 'slicejs-web-framework',
|
|
20
|
-
displayName: 'Slice.js Framework',
|
|
21
|
-
current: '2.4.3',
|
|
22
|
-
latest: '2.5.0',
|
|
23
|
-
type: 'framework'
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
allCurrent: false
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
manager.displayUpdates = () => {
|
|
30
|
-
calls.display += 1;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
manager.checkAndPromptUpdates = async () => {
|
|
34
|
-
calls.prompted += 1;
|
|
35
|
-
return true;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const originalInfo = Print.info;
|
|
39
|
-
const originalError = Print.error;
|
|
40
|
-
|
|
41
|
-
Print.info = (message) => calls.info.push(message);
|
|
42
|
-
Print.error = (message) => calls.error.push(message);
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const result = await manager.notifyAvailableUpdates();
|
|
46
|
-
|
|
47
|
-
assert.equal(result, true);
|
|
48
|
-
assert.equal(calls.display, 1);
|
|
49
|
-
assert.equal(calls.prompted, 0);
|
|
50
|
-
assert.equal(calls.error.length, 0);
|
|
51
|
-
assert.equal(calls.info.length, 1);
|
|
52
|
-
assert.match(calls.info[0], /slice update/);
|
|
53
|
-
} finally {
|
|
54
|
-
Print.info = originalInfo;
|
|
55
|
-
Print.error = originalError;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('notifyAvailableUpdates returns false and prints nothing when no updates are available', async () => {
|
|
60
|
-
const manager = new UpdateManager();
|
|
61
|
-
const calls = {
|
|
62
|
-
display: 0,
|
|
63
|
-
info: []
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
manager.checkForUpdates = async () => ({
|
|
67
|
-
hasUpdates: false,
|
|
68
|
-
updates: [],
|
|
69
|
-
allCurrent: true
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
manager.displayUpdates = () => {
|
|
73
|
-
calls.display += 1;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const originalInfo = Print.info;
|
|
77
|
-
Print.info = (message) => calls.info.push(message);
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const result = await manager.notifyAvailableUpdates();
|
|
81
|
-
|
|
82
|
-
assert.equal(result, false);
|
|
83
|
-
assert.equal(calls.display, 0);
|
|
84
|
-
assert.equal(calls.info.length, 0);
|
|
85
|
-
} finally {
|
|
86
|
-
Print.info = originalInfo;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import Print from '../commands/Print.js';
|
|
4
|
+
import { UpdateManager } from '../commands/utils/updateManager.js';
|
|
5
|
+
|
|
6
|
+
test('notifyAvailableUpdates shows advisory output and never invokes interactive update flow', async () => {
|
|
7
|
+
const manager = new UpdateManager();
|
|
8
|
+
const calls = {
|
|
9
|
+
display: 0,
|
|
10
|
+
prompted: 0,
|
|
11
|
+
info: [],
|
|
12
|
+
error: []
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
manager.checkForUpdates = async () => ({
|
|
16
|
+
hasUpdates: true,
|
|
17
|
+
updates: [
|
|
18
|
+
{
|
|
19
|
+
name: 'slicejs-web-framework',
|
|
20
|
+
displayName: 'Slice.js Framework',
|
|
21
|
+
current: '2.4.3',
|
|
22
|
+
latest: '2.5.0',
|
|
23
|
+
type: 'framework'
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
allCurrent: false
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
manager.displayUpdates = () => {
|
|
30
|
+
calls.display += 1;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
manager.checkAndPromptUpdates = async () => {
|
|
34
|
+
calls.prompted += 1;
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const originalInfo = Print.info;
|
|
39
|
+
const originalError = Print.error;
|
|
40
|
+
|
|
41
|
+
Print.info = (message) => calls.info.push(message);
|
|
42
|
+
Print.error = (message) => calls.error.push(message);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const result = await manager.notifyAvailableUpdates();
|
|
46
|
+
|
|
47
|
+
assert.equal(result, true);
|
|
48
|
+
assert.equal(calls.display, 1);
|
|
49
|
+
assert.equal(calls.prompted, 0);
|
|
50
|
+
assert.equal(calls.error.length, 0);
|
|
51
|
+
assert.equal(calls.info.length, 1);
|
|
52
|
+
assert.match(calls.info[0], /slice update/);
|
|
53
|
+
} finally {
|
|
54
|
+
Print.info = originalInfo;
|
|
55
|
+
Print.error = originalError;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('notifyAvailableUpdates returns false and prints nothing when no updates are available', async () => {
|
|
60
|
+
const manager = new UpdateManager();
|
|
61
|
+
const calls = {
|
|
62
|
+
display: 0,
|
|
63
|
+
info: []
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
manager.checkForUpdates = async () => ({
|
|
67
|
+
hasUpdates: false,
|
|
68
|
+
updates: [],
|
|
69
|
+
allCurrent: true
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
manager.displayUpdates = () => {
|
|
73
|
+
calls.display += 1;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const originalInfo = Print.info;
|
|
77
|
+
Print.info = (message) => calls.info.push(message);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const result = await manager.notifyAvailableUpdates();
|
|
81
|
+
|
|
82
|
+
assert.equal(result, false);
|
|
83
|
+
assert.equal(calls.display, 0);
|
|
84
|
+
assert.equal(calls.info.length, 0);
|
|
85
|
+
} finally {
|
|
86
|
+
Print.info = originalInfo;
|
|
87
|
+
}
|
|
88
|
+
});
|
package/refactor.md
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
# Plan de Refactorización del CLI para MCP
|
|
2
|
-
|
|
3
|
-
## Objetivo
|
|
4
|
-
Separar la lógica de negocio de la presentación CLI para permitir importación directa desde el MCP server, manteniendo **100% de compatibilidad** con el CLI actual.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Estructura Propuesta
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
commands/
|
|
12
|
-
├── createComponent/
|
|
13
|
-
│ ├── createComponent.js # CLI wrapper (presentación)
|
|
14
|
-
│ ├── core.js # ⭐ NUEVO - Lógica pura exportable
|
|
15
|
-
│ └── VisualComponentTemplate.js
|
|
16
|
-
│
|
|
17
|
-
├── listComponents/
|
|
18
|
-
│ ├── listComponents.js # CLI wrapper (presentación)
|
|
19
|
-
│ └── core.js # ⭐ NUEVO - Lógica pura exportable
|
|
20
|
-
│
|
|
21
|
-
├── deleteComponent/
|
|
22
|
-
│ ├── deleteComponent.js # CLI wrapper (presentación)
|
|
23
|
-
│ └── core.js # ⭐ NUEVO - Lógica pura exportable
|
|
24
|
-
│
|
|
25
|
-
├── getComponent/
|
|
26
|
-
│ ├── getComponent.js # CLI wrapper (presentación)
|
|
27
|
-
│ └── core.js # ⭐ NUEVO - Lógica pura exportable
|
|
28
|
-
│
|
|
29
|
-
└── init/
|
|
30
|
-
├── init.js # CLI wrapper (presentación)
|
|
31
|
-
└── core.js # ⭐ NUEVO - Lógica pura exportable
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Principios de Refactorización
|
|
37
|
-
|
|
38
|
-
### 1. Separación de Responsabilidades
|
|
39
|
-
- **core.js**: Lógica pura sin dependencias de CLI (Print, chalk, Table, inquirer)
|
|
40
|
-
- **[comando].js**: Solo presentación CLI, usa funciones del core
|
|
41
|
-
|
|
42
|
-
### 2. Funciones Puras en Core
|
|
43
|
-
- Reciben parámetros explícitos (no usan import.meta.url implícitamente)
|
|
44
|
-
- Retornan objetos con estructura `{ success: boolean, data?, error? }`
|
|
45
|
-
- No imprimen a consola directamente
|
|
46
|
-
- Son agnósticas del entorno (CLI vs MCP)
|
|
47
|
-
|
|
48
|
-
### 3. Compatibilidad Total
|
|
49
|
-
- Los archivos CLI actuales se mantienen funcionales
|
|
50
|
-
- Misma API de comandos
|
|
51
|
-
- Mismo comportamiento observable
|
|
52
|
-
- No romper ningún test existente
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Pasos de Refactorización por Comando
|
|
57
|
-
|
|
58
|
-
### Fase 1: List Components
|
|
59
|
-
|
|
60
|
-
#### Paso 1.1: Análisis del Código Actual
|
|
61
|
-
- Identificar toda la lógica de negocio en `listComponents.js`
|
|
62
|
-
- Separar mentalmente: lógica vs presentación
|
|
63
|
-
- Funciones a extraer: `loadConfig`, `getComponents`, `countComponentFiles`, `listComponentDirectories`
|
|
64
|
-
|
|
65
|
-
#### Paso 1.2: Crear core.js
|
|
66
|
-
- Crear archivo `commands/listComponents/core.js`
|
|
67
|
-
- Extraer funciones de lógica pura
|
|
68
|
-
- Modificar para que acepten `projectPath` como parámetro opcional
|
|
69
|
-
- Retornar objetos estructurados en lugar de imprimir
|
|
70
|
-
|
|
71
|
-
#### Paso 1.3: Adaptar listComponents.js
|
|
72
|
-
- Importar funciones desde `core.js`
|
|
73
|
-
- Mantener solo la lógica de presentación (Print, Table, chalk)
|
|
74
|
-
- Re-exportar funciones del core: `export { getComponents, ... } from './core.js'`
|
|
75
|
-
|
|
76
|
-
#### Paso 1.4: Verificación
|
|
77
|
-
- Ejecutar `slice list` - debe funcionar idéntico
|
|
78
|
-
- Probar importación directa: `import { getComponents } from './commands/listComponents/core.js'`
|
|
79
|
-
- Verificar que retorna datos correctos sin CLI
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
### Fase 2: Create Component
|
|
84
|
-
|
|
85
|
-
#### Paso 2.1: Análisis
|
|
86
|
-
- Identificar lógica: validaciones, generación de templates, creación de archivos
|
|
87
|
-
- Identificar presentación: mensajes de error, comandos de ejemplo
|
|
88
|
-
|
|
89
|
-
#### Paso 2.2: Crear core.js
|
|
90
|
-
- Crear `commands/createComponent/core.js`
|
|
91
|
-
- Extraer: `validateComponentName`, `validateCategory`, `generateTemplate`, `createComponentFiles`
|
|
92
|
-
- Cada función retorna `{ success, data, error }`
|
|
93
|
-
|
|
94
|
-
#### Paso 2.3: Adaptar createComponent.js
|
|
95
|
-
- Importar funciones del core
|
|
96
|
-
- Mantener solo los Print y mensajes de ayuda
|
|
97
|
-
- Re-exportar funciones del core
|
|
98
|
-
|
|
99
|
-
#### Paso 2.4: Verificación
|
|
100
|
-
- Ejecutar `slice component create` - debe funcionar igual
|
|
101
|
-
- Probar importación directa y creación programática
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
### Fase 3: Delete Component
|
|
106
|
-
|
|
107
|
-
#### Paso 3.1: Análisis
|
|
108
|
-
- Identificar lógica: validaciones, verificación de existencia, eliminación de archivos
|
|
109
|
-
- Identificar presentación: mensajes de confirmación, errores
|
|
110
|
-
|
|
111
|
-
#### Paso 3.2: Crear core.js
|
|
112
|
-
- Crear `commands/deleteComponent/core.js`
|
|
113
|
-
- Extraer: `deleteComponentFiles` (con validaciones integradas)
|
|
114
|
-
|
|
115
|
-
#### Paso 3.3: Adaptar deleteComponent.js
|
|
116
|
-
- Importar del core
|
|
117
|
-
- Mantener presentación CLI
|
|
118
|
-
- Re-exportar funciones
|
|
119
|
-
|
|
120
|
-
#### Paso 3.4: Verificación
|
|
121
|
-
- Ejecutar `slice component delete`
|
|
122
|
-
- Verificar funcionamiento idéntico
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
### Fase 4: Get Component (Registry)
|
|
127
|
-
|
|
128
|
-
#### Paso 4.1: Análisis
|
|
129
|
-
- Identificar lógica: descarga de registry, instalación de componentes, actualización
|
|
130
|
-
- Es el más complejo - tiene clase ComponentRegistry
|
|
131
|
-
|
|
132
|
-
#### Paso 4.2: Crear core.js
|
|
133
|
-
- Crear `commands/getComponent/core.js`
|
|
134
|
-
- Extraer clase `ComponentRegistry` limpia (sin inquirer, sin Print)
|
|
135
|
-
- Métodos retornan objetos estructurados
|
|
136
|
-
|
|
137
|
-
#### Paso 4.3: Adaptar getComponent.js
|
|
138
|
-
- Importar ComponentRegistry del core
|
|
139
|
-
- Envolver con lógica interactiva (inquirer) solo en CLI
|
|
140
|
-
- Mantener funciones: `getComponents`, `listComponents`, `syncComponents`
|
|
141
|
-
|
|
142
|
-
#### Paso 4.4: Verificación
|
|
143
|
-
- Probar: `slice get Button`, `slice browse`, `slice sync`
|
|
144
|
-
- Verificar descarga y registro correctos
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
### Fase 5: Init Project
|
|
149
|
-
|
|
150
|
-
#### Paso 5.1: Análisis
|
|
151
|
-
- Identificar lógica: copia de estructuras, configuración de package.json
|
|
152
|
-
- Separar spinners y mensajes visuales
|
|
153
|
-
|
|
154
|
-
#### Paso 5.2: Crear core.js
|
|
155
|
-
- Crear `commands/init/core.js`
|
|
156
|
-
- Extraer: funciones de scaffolding, configuración
|
|
157
|
-
|
|
158
|
-
#### Paso 5.3: Adaptar init.js
|
|
159
|
-
- Importar del core
|
|
160
|
-
- Mantener spinners y presentación
|
|
161
|
-
- Re-exportar funciones
|
|
162
|
-
|
|
163
|
-
#### Paso 5.4: Verificación
|
|
164
|
-
- Ejecutar `slice init` en proyecto nuevo
|
|
165
|
-
- Verificar estructura completa creada
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Patrón de Estructura de Funciones Core
|
|
170
|
-
|
|
171
|
-
### Firma de Función Estándar
|
|
172
|
-
```
|
|
173
|
-
function operationName({ param1, param2, projectPath = null }) {
|
|
174
|
-
// Validaciones
|
|
175
|
-
// Lógica de negocio
|
|
176
|
-
return { success: boolean, data?, error? }
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Objeto de Retorno Estándar
|
|
181
|
-
```javascript
|
|
182
|
-
// Éxito
|
|
183
|
-
{
|
|
184
|
-
success: true,
|
|
185
|
-
data: { ... },
|
|
186
|
-
// Campos adicionales específicos
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Error
|
|
190
|
-
{
|
|
191
|
-
success: false,
|
|
192
|
-
error: "mensaje descriptivo"
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Estrategia de Dependencias
|
|
199
|
-
|
|
200
|
-
### Dependencias Permitidas en core.js
|
|
201
|
-
- ✅ `fs`, `fs-extra`
|
|
202
|
-
- ✅ `path`
|
|
203
|
-
- ✅ `Validations.js` (lógica pura)
|
|
204
|
-
- ✅ Helpers de PathHelper (refactorizar para aceptar projectPath)
|
|
205
|
-
|
|
206
|
-
### Dependencias NO Permitidas en core.js
|
|
207
|
-
- ❌ `Print.js` (específico de CLI)
|
|
208
|
-
- ❌ `chalk` (presentación)
|
|
209
|
-
- ❌ `cli-table3` (presentación)
|
|
210
|
-
- ❌ `inquirer` (interacción CLI)
|
|
211
|
-
- ❌ `ora` (spinners CLI)
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Refactorización de PathHelper
|
|
216
|
-
|
|
217
|
-
### Problema Actual
|
|
218
|
-
PathHelper usa `import.meta.url` internamente, asumiendo que se llama desde dentro del CLI
|
|
219
|
-
|
|
220
|
-
### Solución
|
|
221
|
-
Crear versiones alternativas que acepten `projectPath`:
|
|
222
|
-
|
|
223
|
-
```
|
|
224
|
-
// Versión CLI (actual)
|
|
225
|
-
getSrcPath(import.meta.url, ...paths)
|
|
226
|
-
|
|
227
|
-
// Versión core (nueva)
|
|
228
|
-
getProjectSrcPath(projectPath, ...paths)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
O mejor: modificar funciones existentes para aceptar ambos modos:
|
|
232
|
-
```
|
|
233
|
-
getSrcPath(importMetaUrlOrProjectPath, ...paths)
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## Testing de la Refactorización
|
|
239
|
-
|
|
240
|
-
### Test 1: CLI Functionality
|
|
241
|
-
Para cada comando refactorizado:
|
|
242
|
-
- Ejecutar comando CLI
|
|
243
|
-
- Verificar output idéntico al anterior
|
|
244
|
-
- Verificar archivos creados/modificados
|
|
245
|
-
|
|
246
|
-
### Test 2: Importación Directa
|
|
247
|
-
```javascript
|
|
248
|
-
// test-mcp-import.js
|
|
249
|
-
import { getComponents } from './commands/listComponents/core.js';
|
|
250
|
-
|
|
251
|
-
const result = getComponents('/ruta/proyecto');
|
|
252
|
-
console.log(result); // Debe retornar objeto con componentes
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Test 3: Sin Dependencias CLI
|
|
256
|
-
Verificar que core.js no tiene imports de Print, chalk, etc:
|
|
257
|
-
```bash
|
|
258
|
-
grep -r "from.*Print" commands/*/core.js # debe estar vacío
|
|
259
|
-
grep -r "from.*chalk" commands/*/core.js # debe estar vacío
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## Orden de Implementación Recomendado
|
|
265
|
-
|
|
266
|
-
1. **listComponents** (más simple, establece patrón)
|
|
267
|
-
2. **createComponent** (complejidad media)
|
|
268
|
-
3. **deleteComponent** (similar a create)
|
|
269
|
-
4. **getComponent** (más complejo, tiene clase)
|
|
270
|
-
5. **init** (el más complejo)
|
|
271
|
-
|