@shellui/cli 0.0.18 → 0.0.20
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/package.json +2 -2
- package/src/commands/build.js +4 -9
- package/src/commands/start.js +171 -10
- package/src/utils/index.js +4 -0
- package/src/utils/service-worker-plugin.js +0 -5
- package/src/utils/vite.js +125 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shellui/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "ShellUI CLI - Command-line tool for ShellUI",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"tsx": "^4.21.0",
|
|
31
31
|
"vite": "7.3.1",
|
|
32
32
|
"workbox-build": "^7.1.0",
|
|
33
|
-
"@shellui/core": "0.0.
|
|
33
|
+
"@shellui/core": "0.0.20"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
package/src/commands/build.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
createResolveAlias,
|
|
11
11
|
createPostCSSConfig,
|
|
12
12
|
createViteDefine,
|
|
13
|
+
createViteResolveConfig,
|
|
13
14
|
resolvePackagePath,
|
|
14
15
|
} from '../utils/index.js';
|
|
15
16
|
|
|
@@ -76,6 +77,7 @@ export async function buildCommand(root = '.') {
|
|
|
76
77
|
// Get core package paths
|
|
77
78
|
const corePackagePath = resolvePackagePath('@shellui/core');
|
|
78
79
|
const coreSrcPath = getCoreSrcPath();
|
|
80
|
+
const resolveConfig = createViteResolveConfig();
|
|
79
81
|
const resolveAlias = createResolveAlias();
|
|
80
82
|
const postcssConfig = createPostCSSConfig();
|
|
81
83
|
|
|
@@ -86,14 +88,8 @@ export async function buildCommand(root = '.') {
|
|
|
86
88
|
plugins: [react()],
|
|
87
89
|
define: createViteDefine(config),
|
|
88
90
|
resolve: {
|
|
91
|
+
...resolveConfig,
|
|
89
92
|
alias: resolveAlias,
|
|
90
|
-
// Dedupe React and core package to prevent duplicate module instances
|
|
91
|
-
// This is critical for React Context to work correctly in micro-frontends
|
|
92
|
-
dedupe: ['react', 'react-dom', '@shellui/core'],
|
|
93
|
-
// Ensure consistent module resolution to prevent duplicate instances
|
|
94
|
-
// when the same module is imported via different paths (relative, alias, with/without extensions)
|
|
95
|
-
conditions: ['import', 'module', 'browser', 'default'],
|
|
96
|
-
preserveSymlinks: false,
|
|
97
93
|
},
|
|
98
94
|
css: {
|
|
99
95
|
postcss: postcssConfig,
|
|
@@ -131,9 +127,8 @@ export async function buildCommand(root = '.') {
|
|
|
131
127
|
root: coreSrcPath,
|
|
132
128
|
define: createViteDefine(config),
|
|
133
129
|
resolve: {
|
|
130
|
+
...resolveConfig,
|
|
134
131
|
alias: resolveAlias,
|
|
135
|
-
// Dedupe React and core package to prevent duplicate module instances
|
|
136
|
-
dedupe: ['react', 'react-dom', '@shellui/core'],
|
|
137
132
|
},
|
|
138
133
|
build: {
|
|
139
134
|
outDir: distPath,
|
package/src/commands/start.js
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
createResolveAlias,
|
|
10
10
|
createPostCSSConfig,
|
|
11
11
|
createViteDefine,
|
|
12
|
+
createViteResolveConfig,
|
|
13
|
+
createViteOptimizeDepsConfig,
|
|
14
|
+
getDedupeList,
|
|
15
|
+
getShelluiExcludePackages,
|
|
12
16
|
resolvePackagePath,
|
|
13
17
|
} from '../utils/index.js';
|
|
14
18
|
import { serviceWorkerDevPlugin } from '../utils/service-worker-plugin.js';
|
|
@@ -18,6 +22,74 @@ let configWatcher = null;
|
|
|
18
22
|
let restartTimeout = null;
|
|
19
23
|
let isFirstStart = true;
|
|
20
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Plugin to prevent Vite from creating cache files inside @shellui/core
|
|
27
|
+
* This ensures all cache is created only in the project root's node_modules/.vite
|
|
28
|
+
*/
|
|
29
|
+
function preventNestedCachePlugin(viteCacheDir) {
|
|
30
|
+
return {
|
|
31
|
+
name: 'prevent-nested-cache',
|
|
32
|
+
configResolved(config) {
|
|
33
|
+
if (config.cacheDir && !path.isAbsolute(config.cacheDir)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`cacheDir must be absolute path, got: ${config.cacheDir}. This prevents cache creation inside @shellui/core`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
buildStart() {
|
|
40
|
+
// Check if any .vite directories exist inside @shellui/core and warn/remove them
|
|
41
|
+
const corePackagePath = resolvePackagePath('@shellui/core');
|
|
42
|
+
const nestedViteCache = path.join(corePackagePath, 'node_modules', '.vite');
|
|
43
|
+
if (fs.existsSync(nestedViteCache)) {
|
|
44
|
+
console.warn(
|
|
45
|
+
pc.yellow(
|
|
46
|
+
`⚠️ Found nested .vite cache at ${nestedViteCache}. This should not exist. Removing...`,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
try {
|
|
50
|
+
fs.rmSync(nestedViteCache, { recursive: true, force: true });
|
|
51
|
+
console.log(pc.green(`✅ Removed nested cache directory`));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(pc.red(`❌ Failed to remove nested cache: ${e.message}`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Also remove any optimized @shellui/core files from .vite/deps
|
|
58
|
+
const viteDepsDir = path.join(viteCacheDir, 'deps');
|
|
59
|
+
if (fs.existsSync(viteDepsDir)) {
|
|
60
|
+
try {
|
|
61
|
+
const files = fs.readdirSync(viteDepsDir);
|
|
62
|
+
const shelluiFiles = files.filter(
|
|
63
|
+
(f) => f.startsWith('@shellui') || f.startsWith('@_features'),
|
|
64
|
+
);
|
|
65
|
+
if (shelluiFiles.length > 0) {
|
|
66
|
+
console.warn(
|
|
67
|
+
pc.yellow(
|
|
68
|
+
`⚠️ Found optimized @shellui/core files in .vite/deps. Removing ${shelluiFiles.length} files...`,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
shelluiFiles.forEach((file) => {
|
|
72
|
+
const filePath = path.join(viteDepsDir, file);
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(filePath);
|
|
75
|
+
const mapPath = filePath + '.map';
|
|
76
|
+
if (fs.existsSync(mapPath)) {
|
|
77
|
+
fs.unlinkSync(mapPath);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// Ignore errors
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
console.log(pc.green(`✅ Removed optimized @shellui/core files`));
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Ignore errors reading directory
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
21
93
|
/**
|
|
22
94
|
* Start the Vite server with current configuration
|
|
23
95
|
* @param {string} root - Root directory
|
|
@@ -33,25 +105,114 @@ async function startServer(root, cwd, shouldOpen = false) {
|
|
|
33
105
|
const corePackagePath = resolvePackagePath('@shellui/core');
|
|
34
106
|
const coreSrcPath = getCoreSrcPath();
|
|
35
107
|
|
|
108
|
+
// Get project root node_modules path to ensure all modules resolve from root
|
|
109
|
+
const projectRootNodeModules = path.resolve(cwd, root, 'node_modules');
|
|
110
|
+
// CRITICAL: Set cacheDir to absolute path in project root to prevent Vite from creating
|
|
111
|
+
// cache inside @shellui/core/node_modules/.vite/deps
|
|
112
|
+
const viteCacheDir = path.resolve(cwd, root, 'node_modules', '.vite');
|
|
113
|
+
|
|
36
114
|
// Check if static folder exists in project root
|
|
37
115
|
const staticPath = path.resolve(cwd, root, 'static');
|
|
38
116
|
const publicDir = fs.existsSync(staticPath) ? staticPath : false;
|
|
39
117
|
|
|
118
|
+
const resolveConfig = createViteResolveConfig();
|
|
119
|
+
const resolveAlias = createResolveAlias();
|
|
120
|
+
|
|
40
121
|
const server = await createServer({
|
|
41
122
|
root: coreSrcPath,
|
|
42
|
-
|
|
123
|
+
// Force cacheDir to project root - this prevents Vite from creating cache
|
|
124
|
+
// relative to root (which would be inside @shellui/core)
|
|
125
|
+
cacheDir: viteCacheDir,
|
|
126
|
+
plugins: [
|
|
127
|
+
react(),
|
|
128
|
+
serviceWorkerDevPlugin(corePackagePath, coreSrcPath),
|
|
129
|
+
preventNestedCachePlugin(viteCacheDir),
|
|
130
|
+
{
|
|
131
|
+
name: 'prevent-shellui-optimization',
|
|
132
|
+
enforce: 'pre',
|
|
133
|
+
config(config) {
|
|
134
|
+
if (!config.optimizeDeps) {
|
|
135
|
+
config.optimizeDeps = {};
|
|
136
|
+
}
|
|
137
|
+
const originalExclude = config.optimizeDeps.exclude || [];
|
|
138
|
+
const excludeArray = Array.isArray(originalExclude)
|
|
139
|
+
? [...originalExclude]
|
|
140
|
+
: [originalExclude].filter(Boolean);
|
|
141
|
+
|
|
142
|
+
// Dynamically get all @shellui/* packages that should be excluded
|
|
143
|
+
const shelluiExcludePackages = getShelluiExcludePackages();
|
|
144
|
+
shelluiExcludePackages.forEach((pkg) => {
|
|
145
|
+
if (!excludeArray.includes(pkg)) {
|
|
146
|
+
excludeArray.push(pkg);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
config.optimizeDeps.exclude = excludeArray;
|
|
151
|
+
},
|
|
152
|
+
resolveId(id, importer) {
|
|
153
|
+
if (id && typeof id === 'string') {
|
|
154
|
+
if (id.includes('@_features') || id.includes('@shellui/core/src/')) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
},
|
|
160
|
+
load(id) {
|
|
161
|
+
if (id && typeof id === 'string' && id.includes('@_features')) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
},
|
|
166
|
+
transform(code, id) {
|
|
167
|
+
if (
|
|
168
|
+
id &&
|
|
169
|
+
typeof id === 'string' &&
|
|
170
|
+
(id.includes('@shellui/core/src/') || id.includes('@_features'))
|
|
171
|
+
) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
},
|
|
176
|
+
configureServer(server) {
|
|
177
|
+
server.middlewares.use((req, res, next) => {
|
|
178
|
+
if (
|
|
179
|
+
req.url &&
|
|
180
|
+
(req.url.includes('@_features') || req.url.includes('/.vite/deps/@shellui'))
|
|
181
|
+
) {
|
|
182
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
183
|
+
res.end(
|
|
184
|
+
'Optimized @shellui/core files are disabled. Loading from source instead.',
|
|
185
|
+
);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
next();
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
43
193
|
define: createViteDefine(config),
|
|
44
194
|
resolve: {
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
// This
|
|
48
|
-
|
|
49
|
-
dedupe: ['react', 'react-dom', '@shellui/core'],
|
|
50
|
-
// Ensure consistent module resolution to prevent duplicate instances
|
|
51
|
-
// when the same module is imported via different paths (relative, alias, with/without extensions)
|
|
52
|
-
conditions: ['import', 'module', 'browser', 'default'],
|
|
53
|
-
// Don't preserve symlinks - ensures consistent resolution in monorepos
|
|
195
|
+
...resolveConfig,
|
|
196
|
+
// Dynamically dedupe React, ReactDOM, @shellui packages, and all @shellui/core dependencies
|
|
197
|
+
// This ensures all modules resolve from project root to prevent duplicate instances
|
|
198
|
+
dedupe: getDedupeList(),
|
|
54
199
|
preserveSymlinks: false,
|
|
200
|
+
alias: {
|
|
201
|
+
...resolveAlias,
|
|
202
|
+
// Force @shellui/core to always resolve from project root to prevent duplicates
|
|
203
|
+
'@shellui/core': corePackagePath,
|
|
204
|
+
},
|
|
205
|
+
conditions: ['import', 'module', 'browser', 'default'],
|
|
206
|
+
mainFields: ['browser', 'module', 'jsnext:main', 'jsnext', 'main'],
|
|
207
|
+
},
|
|
208
|
+
optimizeDeps: {
|
|
209
|
+
// Dynamically exclude @shellui packages and include all @shellui/core dependencies
|
|
210
|
+
// This ensures dependencies are optimized from project root, preventing nested resolution
|
|
211
|
+
...createViteOptimizeDepsConfig(),
|
|
212
|
+
force: false,
|
|
213
|
+
esbuildOptions: {
|
|
214
|
+
absWorkingDir: path.resolve(cwd, root),
|
|
215
|
+
},
|
|
55
216
|
},
|
|
56
217
|
css: {
|
|
57
218
|
postcss: createPostCSSConfig(),
|
package/src/utils/index.js
CHANGED
|
@@ -35,11 +35,6 @@ export function serviceWorkerDevPlugin(corePackagePath, coreSrcPath) {
|
|
|
35
35
|
plugins: [react()],
|
|
36
36
|
resolve: {
|
|
37
37
|
alias: createResolveAlias(),
|
|
38
|
-
// Dedupe React and core package to prevent duplicate module instances
|
|
39
|
-
dedupe: ['react', 'react-dom', '@shellui/core'],
|
|
40
|
-
// Ensure consistent module resolution to prevent duplicate instances
|
|
41
|
-
conditions: ['import', 'module', 'browser', 'default'],
|
|
42
|
-
preserveSymlinks: false,
|
|
43
38
|
},
|
|
44
39
|
build: {
|
|
45
40
|
write: false,
|
package/src/utils/vite.js
CHANGED
|
@@ -38,19 +38,14 @@ export function getCoreSrcPath() {
|
|
|
38
38
|
* Always sets '@' to core/src. Sets '@shellui/sdk' to source entry when in
|
|
39
39
|
* workspace mode; omits the alias when installed from npm so Vite resolves
|
|
40
40
|
* through the package's exports field (dist/index.js).
|
|
41
|
-
*
|
|
42
|
-
* Also adds specific aliases for config module to ensure all imports
|
|
43
|
-
* (relative, alias, or with extensions) resolve to the same module instance.
|
|
44
|
-
* This prevents React Context duplication issues.
|
|
45
41
|
* @returns {Object} Vite resolve.alias object
|
|
46
42
|
*/
|
|
47
43
|
export function createResolveAlias() {
|
|
48
44
|
const corePackagePath = resolvePackagePath('@shellui/core');
|
|
49
|
-
const coreSrcPath = path.join(corePackagePath, 'src');
|
|
50
45
|
const sdkEntry = resolveSdkEntry();
|
|
51
46
|
|
|
52
47
|
const alias = {
|
|
53
|
-
'@':
|
|
48
|
+
'@': path.join(corePackagePath, 'src'),
|
|
54
49
|
};
|
|
55
50
|
|
|
56
51
|
if (sdkEntry) {
|
|
@@ -106,3 +101,127 @@ export function createViteDefine(config) {
|
|
|
106
101
|
__SHELLUI_SENTRY_RELEASE__: sentry?.release ? JSON.stringify(sentry.release) : 'undefined',
|
|
107
102
|
};
|
|
108
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create Vite resolve configuration with deduplication.
|
|
107
|
+
* Prevents duplicate React instances and @shellui/core modules when running
|
|
108
|
+
* from node_modules, which can cause context provider issues in microfrontends.
|
|
109
|
+
*
|
|
110
|
+
* Dynamically includes all @shellui/core dependencies to ensure they resolve
|
|
111
|
+
* from project root, preventing nested node_modules resolution issues.
|
|
112
|
+
* @returns {Object} Vite resolve configuration with dedupe
|
|
113
|
+
*/
|
|
114
|
+
export function createViteResolveConfig() {
|
|
115
|
+
return {
|
|
116
|
+
dedupe: getDedupeList(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get dependencies from @shellui/core package.json
|
|
122
|
+
* Reads the package.json and extracts all dependencies (excluding @shellui/* packages)
|
|
123
|
+
* @returns {string[]} Array of dependency package names
|
|
124
|
+
*/
|
|
125
|
+
function getCoreDependencies() {
|
|
126
|
+
try {
|
|
127
|
+
const corePackagePath = resolvePackagePath('@shellui/core');
|
|
128
|
+
const packageJsonPath = path.join(corePackagePath, 'package.json');
|
|
129
|
+
|
|
130
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
131
|
+
console.warn(`Warning: Could not find @shellui/core package.json at ${packageJsonPath}`);
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
136
|
+
const dependencies = packageJson.dependencies || {};
|
|
137
|
+
|
|
138
|
+
// Extract all dependency names, excluding @shellui/* packages
|
|
139
|
+
return Object.keys(dependencies).filter(
|
|
140
|
+
(dep) => !dep.startsWith('@shellui/'),
|
|
141
|
+
);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.warn(`Warning: Failed to read @shellui/core dependencies: ${e.message}`);
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get packages that should be excluded from optimization
|
|
150
|
+
* These are @shellui/* packages that should always load from source
|
|
151
|
+
* @returns {string[]} Array of package names to exclude
|
|
152
|
+
*/
|
|
153
|
+
export function getShelluiExcludePackages() {
|
|
154
|
+
try {
|
|
155
|
+
const corePackagePath = resolvePackagePath('@shellui/core');
|
|
156
|
+
const packageJsonPath = path.join(corePackagePath, 'package.json');
|
|
157
|
+
|
|
158
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
159
|
+
return ['@shellui/core', '@shellui/sdk'];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
163
|
+
const dependencies = packageJson.dependencies || {};
|
|
164
|
+
|
|
165
|
+
// Extract @shellui/* packages that should be excluded
|
|
166
|
+
const shelluiPackages = Object.keys(dependencies).filter(
|
|
167
|
+
(dep) => dep.startsWith('@shellui/'),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Always include @shellui/core and @shellui/sdk
|
|
171
|
+
const exclude = ['@shellui/core', '@shellui/sdk'];
|
|
172
|
+
shelluiPackages.forEach((pkg) => {
|
|
173
|
+
if (!exclude.includes(pkg)) {
|
|
174
|
+
exclude.push(pkg);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return exclude;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.warn(`Warning: Failed to read @shellui/* packages: ${e.message}`);
|
|
181
|
+
return ['@shellui/core', '@shellui/sdk'];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create Vite optimizeDeps configuration to exclude @shellui/core from pre-bundling.
|
|
187
|
+
* This prevents Vite from creating duplicate module instances during dependency optimization,
|
|
188
|
+
* which is critical for React Context to work correctly in microfrontend iframe scenarios.
|
|
189
|
+
*
|
|
190
|
+
* Dynamically reads dependencies from @shellui/core to ensure all new dependencies
|
|
191
|
+
* are automatically included in optimization.
|
|
192
|
+
*
|
|
193
|
+
* Note: We do NOT exclude React/ReactDOM here because Vite needs to optimize them.
|
|
194
|
+
* The resolve.dedupe configuration handles ensuring only one React instance is used.
|
|
195
|
+
* @returns {Object} Vite optimizeDeps configuration
|
|
196
|
+
*/
|
|
197
|
+
export function createViteOptimizeDepsConfig() {
|
|
198
|
+
const coreDependencies = getCoreDependencies();
|
|
199
|
+
const excludePackages = getShelluiExcludePackages();
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
exclude: excludePackages,
|
|
203
|
+
include: coreDependencies,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get deduplication list for Vite resolve configuration
|
|
209
|
+
* Includes React, ReactDOM, @shellui packages, and all @shellui/core dependencies
|
|
210
|
+
* @returns {string[]} Array of package names to dedupe
|
|
211
|
+
*/
|
|
212
|
+
export function getDedupeList() {
|
|
213
|
+
const coreDependencies = getCoreDependencies();
|
|
214
|
+
const shelluiPackages = getShelluiExcludePackages();
|
|
215
|
+
|
|
216
|
+
// Base dedupe list: React, ReactDOM, and @shellui packages
|
|
217
|
+
const dedupe = ['react', 'react-dom', ...shelluiPackages];
|
|
218
|
+
|
|
219
|
+
// Add all core dependencies to ensure they resolve from project root
|
|
220
|
+
coreDependencies.forEach((dep) => {
|
|
221
|
+
if (!dedupe.includes(dep)) {
|
|
222
|
+
dedupe.push(dep);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return dedupe;
|
|
227
|
+
}
|