slicejs-cli 3.1.0 → 3.3.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/.github/workflows/docs-render-cicd.yml +65 -0
- package/CODE_OF_CONDUCT.md +126 -0
- package/ECOSYSTEM.md +9 -0
- package/LICENSE +21 -0
- package/README.md +104 -308
- package/client.js +644 -557
- 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 +261 -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 +538 -0
- 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/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +182 -0
- package/package.json +46 -46
- package/post.js +65 -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/types-generator.test.js +356 -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
|
+
});
|
|
@@ -0,0 +1,356 @@
|
|
|
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
|
+
|
|
7
|
+
import {
|
|
8
|
+
ensureEditorConfigForTypes,
|
|
9
|
+
ensureNoCheckInPublicVendorFiles,
|
|
10
|
+
extractStaticPropsFromSource,
|
|
11
|
+
generateDeclarationContent,
|
|
12
|
+
generateTypesFile
|
|
13
|
+
} from '../commands/types/types.js';
|
|
14
|
+
|
|
15
|
+
test('extractStaticPropsFromSource reads static props definitions', () => {
|
|
16
|
+
const source = `
|
|
17
|
+
export default class Button extends HTMLElement {
|
|
18
|
+
static props = {
|
|
19
|
+
value: { type: 'string', default: 'Button', allowedValues: ['Button', 'Submit'] },
|
|
20
|
+
disabled: { type: 'boolean', default: false },
|
|
21
|
+
size: { type: 'number', allowedValues: [12, 16, 20] },
|
|
22
|
+
options: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
schema: {
|
|
25
|
+
theme: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
schema: {
|
|
28
|
+
mode: { type: 'string', allowedValues: ['light', 'dark'] }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
steps: {
|
|
34
|
+
type: 'array',
|
|
35
|
+
items: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
schema: {
|
|
38
|
+
id: { type: 'string', required: true },
|
|
39
|
+
enabled: { type: 'boolean', default: true }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
onClickCallback: { type: 'function', required: true }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const result = extractStaticPropsFromSource(source);
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(result, {
|
|
51
|
+
value: { type: 'string', required: false, allowedValues: ['Button', 'Submit'] },
|
|
52
|
+
disabled: { type: 'boolean', required: false },
|
|
53
|
+
size: { type: 'number', required: false, allowedValues: [12, 16, 20] },
|
|
54
|
+
options: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
required: false,
|
|
57
|
+
schema: {
|
|
58
|
+
theme: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
required: false,
|
|
61
|
+
schema: {
|
|
62
|
+
mode: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
required: false,
|
|
65
|
+
allowedValues: ['light', 'dark']
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
steps: {
|
|
72
|
+
type: 'array',
|
|
73
|
+
required: false,
|
|
74
|
+
items: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
required: false,
|
|
77
|
+
schema: {
|
|
78
|
+
id: { type: 'string', required: true },
|
|
79
|
+
enabled: { type: 'boolean', required: false }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
onClickCallback: { type: 'function', required: true }
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('generateDeclarationContent creates build typing map', () => {
|
|
88
|
+
const content = generateDeclarationContent({
|
|
89
|
+
Button: {
|
|
90
|
+
value: { type: 'string', required: false, allowedValues: ['primary', 'secondary', 'danger'] },
|
|
91
|
+
size: { type: 'number', required: false, allowedValues: [12, 16] },
|
|
92
|
+
disabled: { type: 'boolean', required: false },
|
|
93
|
+
status: { type: 'string', required: false, allowedValues: ['ok', 1] },
|
|
94
|
+
options: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
required: false,
|
|
97
|
+
schema: {
|
|
98
|
+
mode: { type: 'string', required: false, allowedValues: ['light', 'dark'] }
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
steps: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
required: false,
|
|
104
|
+
items: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
required: false,
|
|
107
|
+
schema: {
|
|
108
|
+
id: { type: 'string', required: true }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
FetchManager: {
|
|
114
|
+
baseUrl: { type: 'string', required: true }
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
assert.match(content, /export interface ButtonProps/);
|
|
119
|
+
assert.match(content, /\[key: string\]: unknown;/);
|
|
120
|
+
assert.match(content, /value\?: 'primary' \| 'secondary' \| 'danger';/);
|
|
121
|
+
assert.match(content, /size\?: 12 \| 16;/);
|
|
122
|
+
assert.match(content, /disabled\?: boolean;/);
|
|
123
|
+
assert.match(content, /status\?: string;/);
|
|
124
|
+
assert.match(content, /export interface FetchManagerProps/);
|
|
125
|
+
assert.match(content, /baseUrl: string;/);
|
|
126
|
+
assert.match(content, /export interface SliceComponentPropsMap/);
|
|
127
|
+
assert.match(content, /Button: ButtonProps;/);
|
|
128
|
+
assert.match(content, /FetchManager: FetchManagerProps;/);
|
|
129
|
+
assert.match(content, /options\?: \{/);
|
|
130
|
+
assert.match(content, /mode\?: 'light' \| 'dark';/);
|
|
131
|
+
assert.match(content, /steps\?: \{/);
|
|
132
|
+
assert.match(content, /id: string;/);
|
|
133
|
+
assert.match(content, /}\[\];/);
|
|
134
|
+
assert.match(content, /declare module 'slicejs-web-framework'/);
|
|
135
|
+
assert.match(content, /interface SliceApi \{/);
|
|
136
|
+
assert.match(content, /export type SliceDynamicElement = HTMLElement & Record<string, any>;/);
|
|
137
|
+
assert.match(content, /build<K extends SliceComponentName>/);
|
|
138
|
+
assert.match(content, /getComponent<T extends SliceDynamicElement = SliceDynamicElement>\(/);
|
|
139
|
+
assert.match(content, /componentSliceId: string/);
|
|
140
|
+
assert.match(content, /\): T \| undefined;/);
|
|
141
|
+
assert.match(content, /interface Element \{/);
|
|
142
|
+
assert.match(content, /querySelector<E extends Element = HTMLElement>\(selectors: string\): E \| null;/);
|
|
143
|
+
assert.match(content, /querySelectorAll<E extends Element = HTMLElement>\(selectors: string\): NodeListOf<E>;/);
|
|
144
|
+
assert.match(content, /interface HTMLElement \{/);
|
|
145
|
+
assert.match(content, /\[key: string\]: any;/);
|
|
146
|
+
assert.match(content, /const slice: SliceBuildApi & Record<string, any>;/);
|
|
147
|
+
assert.match(content, /interface EventTarget \{/);
|
|
148
|
+
assert.match(content, /interface Event \{/);
|
|
149
|
+
assert.match(content, /detail: any;/);
|
|
150
|
+
assert.match(content, /currentTarget: any;/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('generateTypesFile creates declaration file from local components', async () => {
|
|
154
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-'));
|
|
155
|
+
const srcDir = path.join(tmpRoot, 'src');
|
|
156
|
+
const visualDir = path.join(srcDir, 'Components', 'Visual', 'Button');
|
|
157
|
+
const noStaticDir = path.join(srcDir, 'Components', 'Visual', 'Tabs');
|
|
158
|
+
const serviceDir = path.join(srcDir, 'Components', 'Service', 'FetchManager');
|
|
159
|
+
const outputFile = path.join(srcDir, 'slice-build.generated.d.ts');
|
|
160
|
+
|
|
161
|
+
fs.mkdirSync(visualDir, { recursive: true });
|
|
162
|
+
fs.mkdirSync(noStaticDir, { recursive: true });
|
|
163
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
164
|
+
|
|
165
|
+
fs.writeFileSync(
|
|
166
|
+
path.join(srcDir, 'sliceConfig.json'),
|
|
167
|
+
JSON.stringify(
|
|
168
|
+
{
|
|
169
|
+
paths: {
|
|
170
|
+
components: {
|
|
171
|
+
Visual: { path: '/Components/Visual', type: 'Visual' },
|
|
172
|
+
Service: { path: '/Components/Service', type: 'Service' }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
null,
|
|
177
|
+
2
|
|
178
|
+
),
|
|
179
|
+
'utf8'
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
fs.writeFileSync(
|
|
183
|
+
path.join(srcDir, 'Components', 'components.js'),
|
|
184
|
+
`const components = {"Button": "Visual", "Tabs": "Visual", "FetchManager": "Service"};\n\nexport default components;\n`,
|
|
185
|
+
'utf8'
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
fs.writeFileSync(
|
|
189
|
+
path.join(visualDir, 'Button.js'),
|
|
190
|
+
`
|
|
191
|
+
export default class Button extends HTMLElement {
|
|
192
|
+
static props = {
|
|
193
|
+
value: { type: 'string', default: 'Button', allowedValues: ['Button', 'Submit'] },
|
|
194
|
+
size: { type: 'number', allowedValues: [12, 16] },
|
|
195
|
+
disabled: { type: 'boolean', default: false }
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
`,
|
|
199
|
+
'utf8'
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
fs.writeFileSync(
|
|
203
|
+
path.join(noStaticDir, 'Tabs.js'),
|
|
204
|
+
`
|
|
205
|
+
export default class Tabs extends HTMLElement {
|
|
206
|
+
constructor() {
|
|
207
|
+
super();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
`,
|
|
211
|
+
'utf8'
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
fs.writeFileSync(
|
|
215
|
+
path.join(serviceDir, 'FetchManager.js'),
|
|
216
|
+
`
|
|
217
|
+
export default class FetchManager extends HTMLElement {
|
|
218
|
+
static props = {
|
|
219
|
+
baseUrl: { type: 'string', required: true }
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
`,
|
|
223
|
+
'utf8'
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const result = await generateTypesFile({
|
|
227
|
+
projectRoot: tmpRoot,
|
|
228
|
+
outputPath: outputFile
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
assert.equal(result.componentsProcessed, 3);
|
|
232
|
+
assert.equal(fs.existsSync(result.outputPath), true);
|
|
233
|
+
|
|
234
|
+
const declaration = fs.readFileSync(result.outputPath, 'utf8');
|
|
235
|
+
assert.match(declaration, /export interface ButtonProps/);
|
|
236
|
+
assert.match(declaration, /value\?: 'Button' \| 'Submit';/);
|
|
237
|
+
assert.match(declaration, /size\?: 12 \| 16;/);
|
|
238
|
+
assert.match(declaration, /export interface TabsProps/);
|
|
239
|
+
assert.match(declaration, /\[key: string\]: unknown;/);
|
|
240
|
+
assert.match(declaration, /export interface FetchManagerProps/);
|
|
241
|
+
assert.match(declaration, /Tabs: TabsProps;/);
|
|
242
|
+
assert.match(declaration, /build<K extends SliceComponentName>/);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('ensureEditorConfigForTypes creates jsconfig when missing', async () => {
|
|
246
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-jsconfig-create-'));
|
|
247
|
+
const outputFile = path.join(tmpRoot, 'src', 'slice-build.generated.d.ts');
|
|
248
|
+
|
|
249
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
250
|
+
fs.writeFileSync(outputFile, 'export {};\n', 'utf8');
|
|
251
|
+
|
|
252
|
+
const result = await ensureEditorConfigForTypes({
|
|
253
|
+
projectRoot: tmpRoot,
|
|
254
|
+
outputPath: outputFile
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
assert.equal(result.mode, 'created_jsconfig');
|
|
258
|
+
|
|
259
|
+
const jsconfigPath = path.join(tmpRoot, 'jsconfig.json');
|
|
260
|
+
assert.equal(fs.existsSync(jsconfigPath), true);
|
|
261
|
+
|
|
262
|
+
const jsconfig = JSON.parse(fs.readFileSync(jsconfigPath, 'utf8'));
|
|
263
|
+
assert.equal(Array.isArray(jsconfig.include), true);
|
|
264
|
+
assert.equal(jsconfig.include.includes('src/Components/**/*.js'), true);
|
|
265
|
+
assert.equal(jsconfig.include.includes('src/**/*.d.ts'), true);
|
|
266
|
+
assert.equal(jsconfig.include.includes('api/**/*.js'), false);
|
|
267
|
+
assert.equal(jsconfig.include.includes('tests/**/*.js'), false);
|
|
268
|
+
assert.equal(jsconfig.include.includes('src/**/*.js'), false);
|
|
269
|
+
assert.equal(jsconfig.compilerOptions.checkJs, true);
|
|
270
|
+
assert.equal(jsconfig.compilerOptions.strictNullChecks, false);
|
|
271
|
+
assert.equal(jsconfig.compilerOptions.noImplicitAny, false);
|
|
272
|
+
assert.equal(jsconfig.compilerOptions.strict, false);
|
|
273
|
+
assert.equal(Array.isArray(jsconfig.exclude), true);
|
|
274
|
+
assert.equal(jsconfig.exclude.includes('src/libs/**'), true);
|
|
275
|
+
assert.equal(jsconfig.exclude.includes('tests/**'), true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('ensureNoCheckInPublicVendorFiles adds ts-nocheck to js files in publicFolders', async () => {
|
|
279
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-publicfolders-'));
|
|
280
|
+
const srcDir = path.join(tmpRoot, 'src');
|
|
281
|
+
const libsDir = path.join(srcDir, 'libs', 'vendor');
|
|
282
|
+
const assetsDir = path.join(srcDir, 'assets', 'scripts');
|
|
283
|
+
|
|
284
|
+
fs.mkdirSync(libsDir, { recursive: true });
|
|
285
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
286
|
+
|
|
287
|
+
fs.writeFileSync(
|
|
288
|
+
path.join(srcDir, 'sliceConfig.json'),
|
|
289
|
+
JSON.stringify(
|
|
290
|
+
{
|
|
291
|
+
publicFolders: ['/libs', '/assets'],
|
|
292
|
+
paths: { components: {} }
|
|
293
|
+
},
|
|
294
|
+
null,
|
|
295
|
+
2
|
|
296
|
+
),
|
|
297
|
+
'utf8'
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const vendorA = path.join(libsDir, 'a.js');
|
|
301
|
+
const vendorB = path.join(assetsDir, 'b.js');
|
|
302
|
+
fs.writeFileSync(vendorA, 'const a = 1;\n', 'utf8');
|
|
303
|
+
fs.writeFileSync(vendorB, '// @ts-nocheck\nconst b = 2;\n', 'utf8');
|
|
304
|
+
|
|
305
|
+
const result = await ensureNoCheckInPublicVendorFiles(tmpRoot);
|
|
306
|
+
|
|
307
|
+
assert.equal(result.scannedFiles, 2);
|
|
308
|
+
assert.equal(result.updatedFiles, 1);
|
|
309
|
+
assert.equal(fs.readFileSync(vendorA, 'utf8').startsWith('// @ts-nocheck\n'), true);
|
|
310
|
+
assert.equal(fs.readFileSync(vendorB, 'utf8').startsWith('// @ts-nocheck\n'), true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('ensureEditorConfigForTypes augments existing jsconfig include list', async () => {
|
|
314
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-jsconfig-update-'));
|
|
315
|
+
const outputFile = path.join(tmpRoot, 'src', 'slice-build.generated.d.ts');
|
|
316
|
+
const jsconfigPath = path.join(tmpRoot, 'jsconfig.json');
|
|
317
|
+
|
|
318
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
319
|
+
fs.writeFileSync(outputFile, 'export {};\n', 'utf8');
|
|
320
|
+
fs.writeFileSync(
|
|
321
|
+
jsconfigPath,
|
|
322
|
+
JSON.stringify(
|
|
323
|
+
{
|
|
324
|
+
compilerOptions: {
|
|
325
|
+
allowJs: true
|
|
326
|
+
},
|
|
327
|
+
include: ['src/Components/**/*.js', 'src/**/*.js', 'api/**/*.js', 'tests/**/*.js']
|
|
328
|
+
},
|
|
329
|
+
null,
|
|
330
|
+
2
|
|
331
|
+
),
|
|
332
|
+
'utf8'
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const result = await ensureEditorConfigForTypes({
|
|
336
|
+
projectRoot: tmpRoot,
|
|
337
|
+
outputPath: outputFile
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
assert.equal(result.mode, 'updated_jsconfig');
|
|
341
|
+
|
|
342
|
+
const jsconfig = JSON.parse(fs.readFileSync(jsconfigPath, 'utf8'));
|
|
343
|
+
assert.equal(jsconfig.include.includes('src/Components/**/*.js'), true);
|
|
344
|
+
assert.equal(jsconfig.include.includes('src/**/*.d.ts'), true);
|
|
345
|
+
assert.equal(jsconfig.include.includes('src/**/*.js'), false);
|
|
346
|
+
assert.equal(jsconfig.include.includes('api/**/*.js'), false);
|
|
347
|
+
assert.equal(jsconfig.include.includes('tests/**/*.js'), false);
|
|
348
|
+
assert.equal(jsconfig.compilerOptions.allowJs, true);
|
|
349
|
+
assert.equal(jsconfig.compilerOptions.checkJs, true);
|
|
350
|
+
assert.equal(jsconfig.compilerOptions.noImplicitAny, false);
|
|
351
|
+
assert.equal(jsconfig.compilerOptions.strictNullChecks, false);
|
|
352
|
+
assert.equal(jsconfig.compilerOptions.strict, false);
|
|
353
|
+
assert.equal(Array.isArray(jsconfig.exclude), true);
|
|
354
|
+
assert.equal(jsconfig.exclude.includes('src/libs/**'), true);
|
|
355
|
+
assert.equal(jsconfig.exclude.includes('tests/**'), true);
|
|
356
|
+
});
|