@midscene/test 1.17.8-beta-20261106101231.0 → 1.17.8
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/dist/es/154.mjs +2 -0
- package/dist/es/164.mjs +1469 -0
- package/dist/es/164.mjs.map +1 -0
- package/dist/es/183.mjs +295 -0
- package/dist/es/183.mjs.map +1 -0
- package/dist/es/1~112.mjs +372 -0
- package/dist/es/1~112.mjs.map +1 -0
- package/dist/es/209.mjs +1 -0
- package/dist/es/299.mjs +1886 -0
- package/dist/es/299.mjs.map +1 -0
- package/dist/es/398.mjs +5 -0
- package/dist/es/398.mjs.map +1 -0
- package/dist/es/431.mjs +375 -0
- package/dist/es/431.mjs.map +1 -0
- package/dist/es/470.mjs +1 -0
- package/dist/es/487.mjs +1 -0
- package/dist/es/629.mjs +1 -0
- package/dist/es/793.mjs +1 -0
- package/dist/es/822.mjs +1 -0
- package/dist/es/946.mjs +1 -0
- package/dist/es/cli/index.mjs +3 -2196
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/cli/model-command.mjs +1 -296
- package/dist/es/index.mjs +10 -409
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/midscene/index.mjs +9 -9
- package/dist/es/midscene/index.mjs.map +1 -1
- package/dist/es/runtime/index.mjs +4 -5
- package/dist/es/runtime/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +1 -3786
- package/dist/lib/0~703.js.map +1 -1
- package/dist/lib/cli/index.js +45 -58
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/cli/model-command.js +1 -1
- package/dist/lib/cli/model-command.js.map +1 -1
- package/dist/lib/index.js +8 -37
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/midscene/index.js +1 -1
- package/dist/lib/midscene/index.js.map +1 -1
- package/dist/lib/runtime/index.js +1 -1
- package/dist/lib/runtime/index.js.map +1 -1
- package/dist/lib/test-cli.js +884 -877
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/types/cli/index.d.ts +4 -0
- package/package.json +20 -21
- package/dist/es/1~4.mjs +0 -383
- package/dist/es/1~4.mjs.map +0 -1
- package/dist/es/1~731.mjs +0 -306
- package/dist/es/1~731.mjs.map +0 -1
- package/dist/es/cli/model-command.mjs.map +0 -1
- package/dist/es/test-cli.mjs.map +0 -1
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { confirm as prompts_confirm, input, select as prompts_select } from "@inquirer/prompts";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import yargs from "yargs/yargs";
|
|
4
|
+
import { basename, readFileSync, readdirSync, dirname, existsSync, resolve, mkdirSync, lstatSync, relative, writeFileSync, join } from "./154.mjs";
|
|
5
|
+
import { package_namespaceObject } from "./164.mjs";
|
|
6
|
+
const createPackageManagers = [
|
|
7
|
+
'npm',
|
|
8
|
+
'pnpm'
|
|
9
|
+
];
|
|
10
|
+
const packageManagerCommands = {
|
|
11
|
+
npm: {
|
|
12
|
+
install: [
|
|
13
|
+
'install',
|
|
14
|
+
'--workspaces=false'
|
|
15
|
+
],
|
|
16
|
+
describe: [
|
|
17
|
+
'exec',
|
|
18
|
+
'--no',
|
|
19
|
+
'--workspaces=false',
|
|
20
|
+
'--',
|
|
21
|
+
'midscene-test',
|
|
22
|
+
'nodes'
|
|
23
|
+
],
|
|
24
|
+
verifyModel: 'npm exec -- midscene-test model verify',
|
|
25
|
+
installChromium: 'npm exec -- playwright install chromium'
|
|
26
|
+
},
|
|
27
|
+
pnpm: {
|
|
28
|
+
install: [
|
|
29
|
+
'install',
|
|
30
|
+
'--ignore-workspace'
|
|
31
|
+
],
|
|
32
|
+
describe: [
|
|
33
|
+
'exec',
|
|
34
|
+
'midscene-test',
|
|
35
|
+
'nodes'
|
|
36
|
+
],
|
|
37
|
+
verifyModel: 'pnpm exec midscene-test model verify',
|
|
38
|
+
installChromium: 'pnpm exec playwright install chromium'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function detectPackageManager(userAgent) {
|
|
42
|
+
return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';
|
|
43
|
+
}
|
|
44
|
+
function checkedPath(path, label) {
|
|
45
|
+
if (!path || path.includes('\\') || path.split('/').some((part)=>!part || '.' === part || '..' === part) || path.startsWith('/') || path.includes(':')) throw new Error(`Invalid ${label}: ${path}`);
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
function loadTemplateManifest(catalogRoot) {
|
|
49
|
+
const manifest = JSON.parse(readFileSync(join(catalogRoot, 'manifest.json'), 'utf8'));
|
|
50
|
+
if (1 !== manifest.schemaVersion || !Array.isArray(manifest.templates)) throw new Error('Unsupported template manifest schema.');
|
|
51
|
+
const ids = new Set();
|
|
52
|
+
const destinations = [];
|
|
53
|
+
for (const entry of manifest.templates){
|
|
54
|
+
if (!entry || 'string' != typeof entry.id || 'string' != typeof entry.sourceDir || 'string' != typeof entry.examplePath || 'string' != typeof entry.projectName || ![
|
|
55
|
+
'create',
|
|
56
|
+
'example-only'
|
|
57
|
+
].includes(entry.exposure) || 'string' != typeof entry.verification) throw new Error('Invalid template manifest entry.');
|
|
58
|
+
checkedPath(entry.id, 'template id');
|
|
59
|
+
checkedPath(entry.sourceDir, 'template source');
|
|
60
|
+
checkedPath(entry.examplePath, 'example destination');
|
|
61
|
+
if (void 0 === entry.referenceCase !== (void 0 === entry.referenceCommit)) throw new Error(`Incomplete reference case for template ${entry.id}.`);
|
|
62
|
+
if (void 0 !== entry.referenceCase) {
|
|
63
|
+
checkedPath(entry.referenceCase, 'reference case');
|
|
64
|
+
if (!/^[0-9a-f]{40}$/.test(entry.referenceCommit ?? '')) throw new Error(`Invalid reference commit for template ${entry.id}.`);
|
|
65
|
+
}
|
|
66
|
+
if (ids.has(entry.id)) throw new Error(`Duplicate template id: ${entry.id}`);
|
|
67
|
+
if (destinations.some((path)=>path === entry.examplePath || path.startsWith(`${entry.examplePath}/`) || entry.examplePath.startsWith(`${path}/`))) throw new Error(`Overlapping example destination: ${entry.examplePath}`);
|
|
68
|
+
ids.add(entry.id);
|
|
69
|
+
destinations.push(entry.examplePath);
|
|
70
|
+
}
|
|
71
|
+
return manifest;
|
|
72
|
+
}
|
|
73
|
+
function renderTemplate(catalogRoot, entry, variables) {
|
|
74
|
+
const source = resolve(catalogRoot, checkedPath(entry.sourceDir, 'template source'));
|
|
75
|
+
let segment = catalogRoot;
|
|
76
|
+
for (const part of entry.sourceDir.split('/')){
|
|
77
|
+
segment = join(segment, part);
|
|
78
|
+
if (lstatSync(segment).isSymbolicLink()) throw new Error(`Template source contains a symbolic link: ${entry.sourceDir}`);
|
|
79
|
+
}
|
|
80
|
+
if (!lstatSync(source).isDirectory()) throw new Error(`Template is not a directory: ${entry.sourceDir}`);
|
|
81
|
+
const files = {};
|
|
82
|
+
const decoder = new TextDecoder('utf-8', {
|
|
83
|
+
fatal: true
|
|
84
|
+
});
|
|
85
|
+
const visit = (directory, prefix)=>{
|
|
86
|
+
for (const item of readdirSync(directory, {
|
|
87
|
+
withFileTypes: true
|
|
88
|
+
})){
|
|
89
|
+
const path = join(directory, item.name);
|
|
90
|
+
const relativePath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
91
|
+
const stat = lstatSync(path);
|
|
92
|
+
if (stat.isSymbolicLink()) throw new Error(`Template contains a symbolic link: ${relativePath}`);
|
|
93
|
+
if (stat.isDirectory()) visit(path, relativePath);
|
|
94
|
+
else if (stat.isFile()) {
|
|
95
|
+
const outputPath = relativePath.replace(/(^|\/)gitignore$/, '$1.gitignore');
|
|
96
|
+
if (Object.hasOwn(files, outputPath)) throw new Error(`Duplicate rendered template file: ${outputPath}`);
|
|
97
|
+
const data = readFileSync(path);
|
|
98
|
+
let content;
|
|
99
|
+
try {
|
|
100
|
+
content = decoder.decode(data);
|
|
101
|
+
} catch {
|
|
102
|
+
files[outputPath] = data;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
files[outputPath] = Buffer.from(content.replace(/\{\{([A-Z_]+)\}\}/g, (_token, key)=>{
|
|
106
|
+
if (!Object.hasOwn(variables, key)) throw new Error(`Unknown template variable ${key} in ${relativePath}`);
|
|
107
|
+
return variables[key];
|
|
108
|
+
}));
|
|
109
|
+
} else throw new Error(`Unsupported template file: ${relativePath}`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
visit(source, '');
|
|
113
|
+
if (!Object.keys(files).length) throw new Error(`Template has no files: ${entry.id}`);
|
|
114
|
+
return files;
|
|
115
|
+
}
|
|
116
|
+
const createPlatforms = [
|
|
117
|
+
'web',
|
|
118
|
+
'android',
|
|
119
|
+
'ios',
|
|
120
|
+
'harmony',
|
|
121
|
+
'computer'
|
|
122
|
+
];
|
|
123
|
+
const createPlatformLabels = {
|
|
124
|
+
web: 'Web (Playwright)',
|
|
125
|
+
android: 'Android',
|
|
126
|
+
ios: 'iOS',
|
|
127
|
+
harmony: 'HarmonyOS',
|
|
128
|
+
computer: 'Desktop (Computer)'
|
|
129
|
+
};
|
|
130
|
+
function create_template_catalogRoot() {
|
|
131
|
+
const packageRoot = resolve(__dirname, '../..');
|
|
132
|
+
const source = resolve(packageRoot, '../../examples/catalog');
|
|
133
|
+
if (existsSync(source)) return source;
|
|
134
|
+
const packed = resolve(packageRoot, 'dist/templates');
|
|
135
|
+
if (existsSync(packed)) return packed;
|
|
136
|
+
throw new Error('Midscene Test templates are missing from this installation.');
|
|
137
|
+
}
|
|
138
|
+
function createProjectFiles(name, platform, packageManager) {
|
|
139
|
+
const root = create_template_catalogRoot();
|
|
140
|
+
const entries = loadTemplateManifest(root).templates.filter((entry)=>'create' === entry.exposure && entry.platform === platform);
|
|
141
|
+
if (1 !== entries.length) throw new Error(`Expected one create template for platform ${platform}.`);
|
|
142
|
+
const commands = packageManagerCommands[packageManager];
|
|
143
|
+
return renderTemplate(root, entries[0], {
|
|
144
|
+
PROJECT_NAME: name,
|
|
145
|
+
MIDSCENE_VERSION: package_namespaceObject.rE,
|
|
146
|
+
INSTALL_COMMAND: `${packageManager} ${commands.install.join(' ')}`,
|
|
147
|
+
RUN_NODES_COMMAND: `${packageManager} run nodes`,
|
|
148
|
+
VERIFY_MODEL_COMMAND: commands.verifyModel,
|
|
149
|
+
TEST_COMMAND: `${packageManager} test`,
|
|
150
|
+
CHROMIUM_INSTALL_COMMAND: commands.installChromium
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const createParser = ()=>yargs().scriptName('midscene-test create').usage('Usage: $0 [directory] [options]').parserConfiguration({
|
|
154
|
+
'parse-positional-numbers': false,
|
|
155
|
+
'greedy-arrays': false,
|
|
156
|
+
'camel-case-expansion': false,
|
|
157
|
+
'boolean-negation': false
|
|
158
|
+
}).option('platform', {
|
|
159
|
+
type: 'string',
|
|
160
|
+
choices: createPlatforms,
|
|
161
|
+
requiresArg: true,
|
|
162
|
+
description: 'Platform preset (prompt if omitted)'
|
|
163
|
+
}).option('package-manager', {
|
|
164
|
+
type: 'string',
|
|
165
|
+
choices: createPackageManagers,
|
|
166
|
+
requiresArg: true,
|
|
167
|
+
description: 'Package manager (prompt if omitted; detect without prompts, falling back to npm)'
|
|
168
|
+
}).option('yes', {
|
|
169
|
+
type: 'boolean',
|
|
170
|
+
alias: 'y',
|
|
171
|
+
default: false,
|
|
172
|
+
description: 'Disable prompts and install dependencies; directory and platform must be supplied'
|
|
173
|
+
}).option('skip-install', {
|
|
174
|
+
type: 'boolean',
|
|
175
|
+
default: false,
|
|
176
|
+
description: 'Create files without installing dependencies or generating Node Specs'
|
|
177
|
+
}).demandCommand(0, 1, '', 'Only one project directory is allowed.').example('$0', 'Choose a directory and platform interactively').example('$0 my-tests --platform web', 'Create a Web project').epilogue("Optionally installs dependencies with the selected package manager. The generated postinstall script creates a Project-specific Node Spec after installation.\nExisting files are never overwritten. Setup and tests are not run during creation.").help('help').alias('help', 'h').version(false).strict().exitProcess(false).showHelpOnFail(false).fail((message, error)=>{
|
|
178
|
+
throw error ?? new Error(message);
|
|
179
|
+
}).wrap(100);
|
|
180
|
+
function parseCreateArgs(args, onOutput) {
|
|
181
|
+
const values = createParser().parseSync(args, {}, (error, _argv, output)=>{
|
|
182
|
+
if (error) throw error;
|
|
183
|
+
if (output) onOutput?.(output);
|
|
184
|
+
});
|
|
185
|
+
const directory = void 0 === values._[0] ? void 0 : String(values._[0]);
|
|
186
|
+
if (void 0 !== directory && !directory.trim()) throw new Error('Project directory must not be empty.');
|
|
187
|
+
return {
|
|
188
|
+
directory,
|
|
189
|
+
platform: values.platform,
|
|
190
|
+
packageManager: values['package-manager'],
|
|
191
|
+
yes: values.yes ?? false,
|
|
192
|
+
skipInstall: values['skip-install'] ?? false,
|
|
193
|
+
help: true === values.help
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const promptDirectory = ()=>input({
|
|
197
|
+
message: 'Project directory:',
|
|
198
|
+
default: 'my-tests',
|
|
199
|
+
validate: (value)=>value.trim().length > 0 || 'Project directory must not be empty.'
|
|
200
|
+
});
|
|
201
|
+
const selectPlatform = ()=>prompts_select({
|
|
202
|
+
message: 'Select a platform:',
|
|
203
|
+
choices: createPlatforms.map((platform)=>({
|
|
204
|
+
name: createPlatformLabels[platform],
|
|
205
|
+
value: platform
|
|
206
|
+
}))
|
|
207
|
+
});
|
|
208
|
+
const selectPackageManager = (defaultValue)=>prompts_select({
|
|
209
|
+
message: 'Select a package manager:',
|
|
210
|
+
choices: createPackageManagers.map((value)=>({
|
|
211
|
+
name: value,
|
|
212
|
+
value
|
|
213
|
+
})),
|
|
214
|
+
default: defaultValue
|
|
215
|
+
});
|
|
216
|
+
const runPackageManager = async (packageManager, args, cwd, captureOutput = false)=>{
|
|
217
|
+
const result = await execa(packageManager, args, {
|
|
218
|
+
cwd,
|
|
219
|
+
stdin: 'inherit',
|
|
220
|
+
stdout: captureOutput ? 'pipe' : 'inherit',
|
|
221
|
+
stderr: 'inherit',
|
|
222
|
+
stripFinalNewline: false
|
|
223
|
+
});
|
|
224
|
+
return result.stdout ?? '';
|
|
225
|
+
};
|
|
226
|
+
const statIfPresent = (path)=>{
|
|
227
|
+
try {
|
|
228
|
+
return lstatSync(path);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if ('ENOENT' === error.code) return;
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
function checkDestinations(root, filenames) {
|
|
235
|
+
const conflicts = [];
|
|
236
|
+
for (const filename of filenames){
|
|
237
|
+
const path = resolve(root, filename);
|
|
238
|
+
if (statIfPresent(path)) conflicts.push(path);
|
|
239
|
+
let parent = dirname(path);
|
|
240
|
+
while(true){
|
|
241
|
+
const stat = statIfPresent(parent);
|
|
242
|
+
if (stat && !stat.isDirectory()) conflicts.push(parent);
|
|
243
|
+
if (parent === root || parent === dirname(parent)) break;
|
|
244
|
+
parent = dirname(parent);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (conflicts.length) throw new Error(`Cannot create project; these paths already exist or are not directories:\n${[
|
|
248
|
+
...new Set(conflicts)
|
|
249
|
+
].join('\n')}\nChoose another directory or move the conflicting files.`);
|
|
250
|
+
}
|
|
251
|
+
function projectSummary(root, cwd, platform, packageManager, installed) {
|
|
252
|
+
const commands = packageManagerCommands[packageManager];
|
|
253
|
+
const steps = [];
|
|
254
|
+
if (root !== resolve(cwd)) {
|
|
255
|
+
const directory = relative(cwd, root) || root;
|
|
256
|
+
steps.push(!/^[a-zA-Z0-9_./\\-]+$/.test(directory) || directory.startsWith('-') || 'win32' !== process.platform && directory.includes('\\') ? 'Open the project directory in your terminal.' : `Open the project\n cd ${directory}`);
|
|
257
|
+
}
|
|
258
|
+
if (!installed) steps.push(`Install dependencies\n ${packageManager} ${commands.install.join(' ')}`);
|
|
259
|
+
steps.push('Configure your model\n Copy .env.example to .env and fill in your model settings.', `Verify the model connection\n ${commands.verifyModel}`, 'web' === platform ? `Install Chromium\n ${commands.installChromium}` : 'computer' === platform ? 'Prepare your desktop\n Follow README.md to set up dependencies and permissions.' : 'Connect your device\n Configure the device connection in .env; see README.md.', `Run your first test\n ${packageManager} test`);
|
|
260
|
+
return [
|
|
261
|
+
'',
|
|
262
|
+
installed ? 'Project ready!' : 'Project files created. Installation skipped.',
|
|
263
|
+
'',
|
|
264
|
+
` Location ${root}`,
|
|
265
|
+
` Platform ${createPlatformLabels[platform]}`,
|
|
266
|
+
'',
|
|
267
|
+
'Next steps',
|
|
268
|
+
'',
|
|
269
|
+
steps.map((step, index)=>` ${index + 1}. ${step}`).join('\n\n'),
|
|
270
|
+
'',
|
|
271
|
+
'Writing your own tests',
|
|
272
|
+
'',
|
|
273
|
+
` midscene-node-spec.${platform}.md${installed ? '' : ' (generated after installation)'}`,
|
|
274
|
+
' Lists the test operations and parameters available in this project.',
|
|
275
|
+
' Refer to it when writing YAML tests, or share it with your AI coding agent.',
|
|
276
|
+
'',
|
|
277
|
+
' More details: README.md',
|
|
278
|
+
''
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
async function runCreateCommand(args, io, overrides = {}) {
|
|
282
|
+
const options = parseCreateArgs(args, (message)=>io.log(message));
|
|
283
|
+
if (options.help) return;
|
|
284
|
+
const services = {
|
|
285
|
+
cwd: process.cwd(),
|
|
286
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
287
|
+
userAgent: process.env.npm_config_user_agent,
|
|
288
|
+
promptDirectory: promptDirectory,
|
|
289
|
+
selectPlatform: selectPlatform,
|
|
290
|
+
selectPackageManager: selectPackageManager,
|
|
291
|
+
confirmInstall: ()=>prompts_confirm({
|
|
292
|
+
message: 'Install dependencies and generate Node Specs now?',
|
|
293
|
+
default: true
|
|
294
|
+
}),
|
|
295
|
+
runPackageManager: runPackageManager,
|
|
296
|
+
...overrides
|
|
297
|
+
};
|
|
298
|
+
if ((!options.directory || !options.platform) && (options.yes || !services.interactive)) throw new Error('Project directory and --platform are required without prompts. Example: midscene-test create my-tests --platform web');
|
|
299
|
+
let directory;
|
|
300
|
+
let platform;
|
|
301
|
+
let packageManager;
|
|
302
|
+
try {
|
|
303
|
+
directory = options.directory ?? (await services.promptDirectory()).trim();
|
|
304
|
+
platform = options.platform ?? await services.selectPlatform();
|
|
305
|
+
const defaultPackageManager = detectPackageManager(services.userAgent);
|
|
306
|
+
packageManager = options.packageManager ?? (services.interactive && !options.yes ? await services.selectPackageManager(defaultPackageManager) : defaultPackageManager);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (error instanceof Error && [
|
|
309
|
+
'ExitPromptError',
|
|
310
|
+
'AbortPromptError'
|
|
311
|
+
].includes(error.name)) throw new Error('Project creation cancelled.');
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
const root = resolve(services.cwd, directory);
|
|
315
|
+
const name = basename(root).toLowerCase().replace(/[^a-z0-9._-]/g, '-').replace(/^[._-]+/, '') || 'midscene-tests';
|
|
316
|
+
const commands = packageManagerCommands[packageManager];
|
|
317
|
+
const files = createProjectFiles(name, platform, packageManager);
|
|
318
|
+
checkDestinations(root, [
|
|
319
|
+
...Object.keys(files),
|
|
320
|
+
`midscene-node-spec.${platform}.md`,
|
|
321
|
+
'pnpm-lock.yaml',
|
|
322
|
+
'package-lock.json',
|
|
323
|
+
'npm-shrinkwrap.json'
|
|
324
|
+
]);
|
|
325
|
+
for (const [filename, content] of Object.entries(files)){
|
|
326
|
+
const path = resolve(root, filename);
|
|
327
|
+
mkdirSync(dirname(path), {
|
|
328
|
+
recursive: true
|
|
329
|
+
});
|
|
330
|
+
writeFileSync(path, content, {
|
|
331
|
+
flag: 'wx'
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
io.log(`Created ${platform} project files in ${root}`);
|
|
335
|
+
let install = !options.skipInstall;
|
|
336
|
+
if (install && services.interactive && !options.yes) try {
|
|
337
|
+
install = await services.confirmInstall();
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error instanceof Error && [
|
|
340
|
+
'ExitPromptError',
|
|
341
|
+
'AbortPromptError'
|
|
342
|
+
].includes(error.name)) throw new Error(`Project creation cancelled. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-spec.${platform}.md.`);
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
if (!install) return void io.log(projectSummary(root, services.cwd, platform, packageManager, false));
|
|
346
|
+
io.log(`Installing dependencies with ${packageManager}...`);
|
|
347
|
+
try {
|
|
348
|
+
await services.runPackageManager(packageManager, commands.install, root);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
throw new Error(`Dependency installation or postinstall Node Spec generation failed. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\n${error instanceof Error ? error.message : String(error)}`, {
|
|
351
|
+
cause: error
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const specPath = resolve(root, `midscene-node-spec.${platform}.md`);
|
|
356
|
+
const generatedByPostinstall = Boolean(statIfPresent(specPath));
|
|
357
|
+
if (!generatedByPostinstall) {
|
|
358
|
+
io.log('Generating Node Spec...');
|
|
359
|
+
await services.runPackageManager(packageManager, commands.describe, root);
|
|
360
|
+
}
|
|
361
|
+
const markdown = readFileSync(specPath, 'utf8');
|
|
362
|
+
if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) throw new Error('nodes did not generate a valid Node Spec.');
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new Error(`Node Spec generation failed. Project files are preserved in ${root}.\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\n${error instanceof Error ? error.message : String(error)}`, {
|
|
365
|
+
cause: error
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
io.log(projectSummary(root, services.cwd, platform, packageManager, true));
|
|
369
|
+
}
|
|
370
|
+
export { runCreateCommand };
|
|
371
|
+
|
|
372
|
+
//# sourceMappingURL=1~112.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1~112.mjs","sources":["../../src/cli/create-package-manager.ts","../../src/cli/template-renderer.ts","../../src/cli/create-template.ts","../../src/cli/create-command.ts"],"sourcesContent":["export const createPackageManagers = ['npm', 'pnpm'] as const;\nexport type CreatePackageManager = (typeof createPackageManagers)[number];\n\nexport const packageManagerCommands: Record<\n CreatePackageManager,\n {\n install: string[];\n describe: string[];\n installChromium: string;\n verifyModel: string;\n }\n> = {\n npm: {\n install: ['install', '--workspaces=false'],\n describe: [\n 'exec',\n '--no',\n '--workspaces=false',\n '--',\n 'midscene-test',\n 'nodes',\n ],\n verifyModel: 'npm exec -- midscene-test model verify',\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n verifyModel: 'pnpm exec midscene-test model verify',\n installChromium: 'pnpm exec playwright install chromium',\n },\n};\n\nexport function detectPackageManager(userAgent?: string): CreatePackageManager {\n return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';\n}\n","import { lstatSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nexport interface TemplateEntry {\n id: string;\n sourceDir: string;\n examplePath: string;\n projectName: string;\n exposure: 'create' | 'example-only';\n platform?: string;\n verification: string;\n referenceCase?: string;\n referenceCommit?: string;\n}\n\nexport interface TemplateManifest {\n schemaVersion: 1;\n templates: TemplateEntry[];\n}\n\n/** Manifest paths are portable relative paths, never paths supplied by a CLI user. */\nfunction checkedPath(path: string, label: string): string {\n if (\n !path ||\n path.includes('\\\\') ||\n path.split('/').some((part) => !part || part === '.' || part === '..') ||\n path.startsWith('/') ||\n path.includes(':')\n ) {\n throw new Error(`Invalid ${label}: ${path}`);\n }\n return path;\n}\n\nexport function loadTemplateManifest(catalogRoot: string): TemplateManifest {\n const manifest = JSON.parse(\n readFileSync(join(catalogRoot, 'manifest.json'), 'utf8'),\n ) as TemplateManifest;\n if (manifest.schemaVersion !== 1 || !Array.isArray(manifest.templates)) {\n throw new Error('Unsupported template manifest schema.');\n }\n const ids = new Set<string>();\n const destinations: string[] = [];\n for (const entry of manifest.templates) {\n if (\n !entry ||\n typeof entry.id !== 'string' ||\n typeof entry.sourceDir !== 'string' ||\n typeof entry.examplePath !== 'string' ||\n typeof entry.projectName !== 'string' ||\n !['create', 'example-only'].includes(entry.exposure) ||\n typeof entry.verification !== 'string'\n ) {\n throw new Error('Invalid template manifest entry.');\n }\n checkedPath(entry.id, 'template id');\n checkedPath(entry.sourceDir, 'template source');\n checkedPath(entry.examplePath, 'example destination');\n if (\n (entry.referenceCase === undefined) !==\n (entry.referenceCommit === undefined)\n ) {\n throw new Error(`Incomplete reference case for template ${entry.id}.`);\n }\n if (entry.referenceCase !== undefined) {\n checkedPath(entry.referenceCase, 'reference case');\n if (!/^[0-9a-f]{40}$/.test(entry.referenceCommit ?? '')) {\n throw new Error(`Invalid reference commit for template ${entry.id}.`);\n }\n }\n if (ids.has(entry.id))\n throw new Error(`Duplicate template id: ${entry.id}`);\n if (\n destinations.some(\n (path) =>\n path === entry.examplePath ||\n path.startsWith(`${entry.examplePath}/`) ||\n entry.examplePath.startsWith(`${path}/`),\n )\n ) {\n throw new Error(`Overlapping example destination: ${entry.examplePath}`);\n }\n ids.add(entry.id);\n destinations.push(entry.examplePath);\n }\n return manifest;\n}\n\nexport function renderTemplate(\n catalogRoot: string,\n entry: TemplateEntry,\n variables: Record<string, string>,\n): Record<string, Buffer> {\n const source = resolve(\n catalogRoot,\n checkedPath(entry.sourceDir, 'template source'),\n );\n let segment = catalogRoot;\n for (const part of entry.sourceDir.split('/')) {\n segment = join(segment, part);\n if (lstatSync(segment).isSymbolicLink()) {\n throw new Error(\n `Template source contains a symbolic link: ${entry.sourceDir}`,\n );\n }\n }\n if (!lstatSync(source).isDirectory()) {\n throw new Error(`Template is not a directory: ${entry.sourceDir}`);\n }\n const files: Record<string, Buffer> = {};\n const decoder = new TextDecoder('utf-8', { fatal: true });\n const visit = (directory: string, prefix: string) => {\n for (const item of readdirSync(directory, { withFileTypes: true })) {\n const path = join(directory, item.name);\n const relativePath = prefix ? `${prefix}/${item.name}` : item.name;\n const stat = lstatSync(path);\n if (stat.isSymbolicLink()) {\n throw new Error(`Template contains a symbolic link: ${relativePath}`);\n }\n if (stat.isDirectory()) {\n visit(path, relativePath);\n } else if (stat.isFile()) {\n // Package managers omit .gitignore from tarballs. Store it as a\n // regular template file and restore the name only when rendering.\n const outputPath = relativePath.replace(\n /(^|\\/)gitignore$/,\n '$1.gitignore',\n );\n if (Object.hasOwn(files, outputPath)) {\n throw new Error(`Duplicate rendered template file: ${outputPath}`);\n }\n const data = readFileSync(path);\n let content: string;\n try {\n content = decoder.decode(data);\n } catch {\n files[outputPath] = data;\n continue;\n }\n files[outputPath] = Buffer.from(\n content.replace(/\\{\\{([A-Z_]+)\\}\\}/g, (_token, key: string) => {\n if (!Object.hasOwn(variables, key)) {\n throw new Error(\n `Unknown template variable ${key} in ${relativePath}`,\n );\n }\n return variables[key];\n }),\n );\n } else {\n throw new Error(`Unsupported template file: ${relativePath}`);\n }\n }\n };\n visit(source, '');\n if (!Object.keys(files).length) {\n throw new Error(`Template has no files: ${entry.id}`);\n }\n return files;\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { version } from '../../package.json';\nimport {\n type CreatePackageManager,\n packageManagerCommands,\n} from './create-package-manager';\nimport { loadTemplateManifest, renderTemplate } from './template-renderer';\n\nexport const createPlatforms = [\n 'web',\n 'android',\n 'ios',\n 'harmony',\n 'computer',\n] as const;\nexport type CreatePlatform = (typeof createPlatforms)[number];\n\nexport const createPlatformLabels: Record<CreatePlatform, string> = {\n web: 'Web (Playwright)',\n android: 'Android',\n ios: 'iOS',\n harmony: 'HarmonyOS',\n computer: 'Desktop (Computer)',\n};\n\nfunction catalogRoot(): string {\n // Source checkout and packed CLI are both two directories below the package root.\n const packageRoot = resolve(__dirname, '../..');\n const source = resolve(packageRoot, '../../examples/catalog');\n if (existsSync(source)) return source;\n const packed = resolve(packageRoot, 'dist/templates');\n if (existsSync(packed)) return packed;\n throw new Error(\n 'Midscene Test templates are missing from this installation.',\n );\n}\n\nexport function createProjectFiles(\n name: string,\n platform: CreatePlatform,\n packageManager: CreatePackageManager,\n): Record<string, Buffer> {\n const root = catalogRoot();\n const entries = loadTemplateManifest(root).templates.filter(\n (entry) => entry.exposure === 'create' && entry.platform === platform,\n );\n if (entries.length !== 1) {\n throw new Error(`Expected one create template for platform ${platform}.`);\n }\n const commands = packageManagerCommands[packageManager];\n return renderTemplate(root, entries[0], {\n PROJECT_NAME: name,\n MIDSCENE_VERSION: version,\n INSTALL_COMMAND: `${packageManager} ${commands.install.join(' ')}`,\n RUN_NODES_COMMAND: `${packageManager} run nodes`,\n VERIFY_MODEL_COMMAND: commands.verifyModel,\n TEST_COMMAND: `${packageManager} test`,\n CHROMIUM_INSTALL_COMMAND: commands.installChromium,\n });\n}\n","import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, relative, resolve } from 'node:path';\nimport { confirm, input, select } from '@inquirer/prompts';\nimport { execa } from 'execa';\nimport yargs from 'yargs/yargs';\nimport {\n type CreatePackageManager,\n createPackageManagers,\n detectPackageManager,\n packageManagerCommands,\n} from './create-package-manager';\nimport {\n type CreatePlatform,\n createPlatformLabels,\n createPlatforms,\n createProjectFiles,\n} from './create-template';\nimport type { TestCliIO } from './test-command';\n\nconst createParser = () =>\n yargs()\n .scriptName('midscene-test create')\n .usage('Usage: $0 [directory] [options]')\n .parserConfiguration({\n 'parse-positional-numbers': false,\n 'greedy-arrays': false,\n 'camel-case-expansion': false,\n 'boolean-negation': false,\n })\n .option('platform', {\n type: 'string',\n choices: createPlatforms,\n requiresArg: true,\n description: 'Platform preset (prompt if omitted)',\n })\n .option('package-manager', {\n type: 'string',\n choices: createPackageManagers,\n requiresArg: true,\n description:\n 'Package manager (prompt if omitted; detect without prompts, falling back to npm)',\n })\n .option('yes', {\n type: 'boolean',\n alias: 'y',\n default: false,\n description:\n 'Disable prompts and install dependencies; directory and platform must be supplied',\n })\n .option('skip-install', {\n type: 'boolean',\n default: false,\n description:\n 'Create files without installing dependencies or generating Node Specs',\n })\n .demandCommand(0, 1, '', 'Only one project directory is allowed.')\n .example('$0', 'Choose a directory and platform interactively')\n .example('$0 my-tests --platform web', 'Create a Web project')\n .epilogue(\n 'Optionally installs dependencies with the selected package manager. The generated postinstall script creates a Project-specific Node Spec after installation.\\nExisting files are never overwritten. Setup and tests are not run during creation.',\n )\n .help('help')\n .alias('help', 'h')\n .version(false)\n .strict()\n .exitProcess(false)\n .showHelpOnFail(false)\n .fail((message, error) => {\n throw error ?? new Error(message);\n })\n .wrap(100);\n\nexport interface CreateOptions {\n directory?: string;\n platform?: CreatePlatform;\n packageManager?: CreatePackageManager;\n yes: boolean;\n skipInstall: boolean;\n help: boolean;\n}\n\nexport function parseCreateArgs(\n args: string[],\n onOutput?: (message: string) => void,\n): CreateOptions {\n const values = createParser().parseSync(args, {}, (error, _argv, output) => {\n if (error) throw error;\n if (output) onOutput?.(output);\n });\n const directory = values._[0] === undefined ? undefined : String(values._[0]);\n if (directory !== undefined && !directory.trim()) {\n throw new Error('Project directory must not be empty.');\n }\n return {\n directory,\n platform: values.platform,\n packageManager: values['package-manager'],\n yes: values.yes ?? false,\n skipInstall: values['skip-install'] ?? false,\n help: values.help === true,\n };\n}\n\nexport interface CreateServices {\n cwd: string;\n interactive: boolean;\n userAgent: string | undefined;\n promptDirectory(): Promise<string>;\n selectPlatform(): Promise<CreatePlatform>;\n selectPackageManager(\n defaultValue: CreatePackageManager,\n ): Promise<CreatePackageManager>;\n confirmInstall(): Promise<boolean>;\n runPackageManager(\n packageManager: CreatePackageManager,\n args: string[],\n cwd: string,\n captureOutput?: boolean,\n ): Promise<string>;\n}\n\nconst promptDirectory = () =>\n input({\n message: 'Project directory:',\n default: 'my-tests',\n validate: (value) =>\n value.trim().length > 0 || 'Project directory must not be empty.',\n });\n\nconst selectPlatform = () =>\n select<CreatePlatform>({\n message: 'Select a platform:',\n choices: createPlatforms.map((platform) => ({\n name: createPlatformLabels[platform],\n value: platform,\n })),\n });\n\nconst selectPackageManager: CreateServices['selectPackageManager'] = (\n defaultValue,\n) =>\n select<CreatePackageManager>({\n message: 'Select a package manager:',\n choices: createPackageManagers.map((value) => ({ name: value, value })),\n default: defaultValue,\n });\n\nconst runPackageManager: CreateServices['runPackageManager'] = async (\n packageManager,\n args,\n cwd,\n captureOutput = false,\n) => {\n const result = await execa(packageManager, args, {\n cwd,\n stdin: 'inherit',\n stdout: captureOutput ? 'pipe' : 'inherit',\n stderr: 'inherit',\n stripFinalNewline: false,\n });\n return result.stdout ?? '';\n};\n\nconst statIfPresent = (path: string) => {\n try {\n return lstatSync(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\n/** Check every destination before writing anything, including dangling links. */\nfunction checkDestinations(root: string, filenames: string[]): void {\n const conflicts: string[] = [];\n for (const filename of filenames) {\n const path = resolve(root, filename);\n if (statIfPresent(path)) conflicts.push(path);\n let parent = dirname(path);\n while (true) {\n const stat = statIfPresent(parent);\n if (stat && !stat.isDirectory()) conflicts.push(parent);\n if (parent === root || parent === dirname(parent)) break;\n parent = dirname(parent);\n }\n }\n if (conflicts.length) {\n throw new Error(\n `Cannot create project; these paths already exist or are not directories:\\n${[...new Set(conflicts)].join('\\n')}\\nChoose another directory or move the conflicting files.`,\n );\n }\n}\n\nfunction projectSummary(\n root: string,\n cwd: string,\n platform: CreatePlatform,\n packageManager: CreatePackageManager,\n installed: boolean,\n): string {\n const commands = packageManagerCommands[packageManager];\n const steps: string[] = [];\n if (root !== resolve(cwd)) {\n const directory = relative(cwd, root) || root;\n // Only print a copyable cd command when it needs no shell-specific escaping.\n steps.push(\n /^[a-zA-Z0-9_./\\\\-]+$/.test(directory) &&\n !directory.startsWith('-') &&\n (process.platform === 'win32' || !directory.includes('\\\\'))\n ? `Open the project\\n cd ${directory}`\n : 'Open the project directory in your terminal.',\n );\n }\n if (!installed) {\n steps.push(\n `Install dependencies\\n ${packageManager} ${commands.install.join(' ')}`,\n );\n }\n steps.push(\n 'Configure your model\\n Copy .env.example to .env and fill in your model settings.',\n `Verify the model connection\\n ${commands.verifyModel}`,\n platform === 'web'\n ? `Install Chromium\\n ${commands.installChromium}`\n : platform === 'computer'\n ? 'Prepare your desktop\\n Follow README.md to set up dependencies and permissions.'\n : 'Connect your device\\n Configure the device connection in .env; see README.md.',\n `Run your first test\\n ${packageManager} test`,\n );\n return [\n '',\n installed\n ? 'Project ready!'\n : 'Project files created. Installation skipped.',\n '',\n ` Location ${root}`,\n ` Platform ${createPlatformLabels[platform]}`,\n '',\n 'Next steps',\n '',\n steps.map((step, index) => ` ${index + 1}. ${step}`).join('\\n\\n'),\n '',\n 'Writing your own tests',\n '',\n ` midscene-node-spec.${platform}.md${installed ? '' : ' (generated after installation)'}`,\n ' Lists the test operations and parameters available in this project.',\n ' Refer to it when writing YAML tests, or share it with your AI coding agent.',\n '',\n ' More details: README.md',\n '',\n ].join('\\n');\n}\n\nexport async function runCreateCommand(\n args: string[],\n io: TestCliIO,\n overrides: Partial<CreateServices> = {},\n): Promise<void> {\n const options = parseCreateArgs(args, (message) => io.log(message));\n if (options.help) return;\n const services: CreateServices = {\n cwd: process.cwd(),\n interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n userAgent: process.env.npm_config_user_agent,\n promptDirectory,\n selectPlatform,\n selectPackageManager,\n confirmInstall: () =>\n confirm({\n message: 'Install dependencies and generate Node Specs now?',\n default: true,\n }),\n runPackageManager,\n ...overrides,\n };\n if (\n (!options.directory || !options.platform) &&\n (options.yes || !services.interactive)\n ) {\n throw new Error(\n 'Project directory and --platform are required without prompts. Example: midscene-test create my-tests --platform web',\n );\n }\n let directory: string;\n let platform: CreatePlatform;\n let packageManager: CreatePackageManager;\n try {\n directory = options.directory ?? (await services.promptDirectory()).trim();\n platform = options.platform ?? (await services.selectPlatform());\n const defaultPackageManager = detectPackageManager(services.userAgent);\n packageManager =\n options.packageManager ??\n (services.interactive && !options.yes\n ? await services.selectPackageManager(defaultPackageManager)\n : defaultPackageManager);\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error('Project creation cancelled.');\n }\n throw error;\n }\n const root = resolve(services.cwd, directory);\n const name =\n basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9._-]/g, '-')\n .replace(/^[._-]+/, '') || 'midscene-tests';\n const commands = packageManagerCommands[packageManager];\n const files = createProjectFiles(name, platform, packageManager);\n checkDestinations(root, [\n ...Object.keys(files),\n `midscene-node-spec.${platform}.md`,\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n ]);\n for (const [filename, content] of Object.entries(files)) {\n const path = resolve(root, filename);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, { flag: 'wx' });\n }\n io.log(`Created ${platform} project files in ${root}`);\n let install = !options.skipInstall;\n if (install && services.interactive && !options.yes) {\n try {\n install = await services.confirmInstall();\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error(\n `Project creation cancelled. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-spec.${platform}.md.`,\n );\n }\n throw error;\n }\n }\n if (!install) {\n io.log(projectSummary(root, services.cwd, platform, packageManager, false));\n return;\n }\n io.log(`Installing dependencies with ${packageManager}...`);\n try {\n await services.runPackageManager(packageManager, commands.install, root);\n } catch (error) {\n throw new Error(\n `Dependency installation or postinstall Node Spec generation failed. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\\n${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n try {\n const specPath = resolve(root, `midscene-node-spec.${platform}.md`);\n const generatedByPostinstall = Boolean(statIfPresent(specPath));\n // Fall back to explicit generation if lifecycle scripts were disabled.\n if (!generatedByPostinstall) {\n io.log('Generating Node Spec...');\n await services.runPackageManager(packageManager, commands.describe, root);\n }\n const markdown = readFileSync(specPath, 'utf8');\n if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) {\n throw new Error('nodes did not generate a valid Node Spec.');\n }\n } catch (error) {\n throw new Error(\n `Node Spec generation failed. Project files are preserved in ${root}.\\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\\n${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n io.log(projectSummary(root, services.cwd, platform, packageManager, true));\n}\n"],"names":["createPackageManagers","packageManagerCommands","detectPackageManager","userAgent","checkedPath","path","label","part","Error","loadTemplateManifest","catalogRoot","manifest","JSON","readFileSync","join","Array","ids","Set","destinations","entry","undefined","renderTemplate","variables","source","resolve","segment","lstatSync","files","decoder","TextDecoder","visit","directory","prefix","item","readdirSync","relativePath","stat","outputPath","Object","data","content","Buffer","_token","key","createPlatforms","createPlatformLabels","packageRoot","__dirname","existsSync","packed","createProjectFiles","name","platform","packageManager","root","entries","commands","version","createParser","yargs","message","error","parseCreateArgs","args","onOutput","values","_argv","output","String","promptDirectory","input","value","selectPlatform","select","selectPackageManager","defaultValue","runPackageManager","cwd","captureOutput","result","execa","statIfPresent","checkDestinations","filenames","conflicts","filename","parent","dirname","projectSummary","installed","steps","relative","process","step","index","runCreateCommand","io","overrides","options","services","Boolean","confirm","defaultPackageManager","basename","mkdirSync","writeFileSync","install","specPath","generatedByPostinstall","markdown"],"mappings":"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"}
|
package/dist/es/209.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { globalConfigManager, globalModelConfigManager } from "@midscene/shared/env";
|