pixifact-cli 0.6.0 → 0.7.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/LICENSE +21 -0
- package/README.md +22 -9
- package/editor/assets/browserAll-DePxC4RK.js +1 -0
- package/editor/assets/{index-gCKoe0w8.css → index-D3t5XxiB.css} +1 -1
- package/editor/assets/{index-D8cbY_GQ.js → index-DoPhYWF1.js} +83 -83
- package/editor/assets/init-CuKNsO_k.js +1 -0
- package/editor/assets/webworkerAll-DM9kdU0t.js +1 -0
- package/editor/index.html +2 -2
- package/package.json +3 -3
- package/src/automation.ts +1 -1
- package/src/editorServer.ts +6 -1
- package/src/pixifact-cli.ts +45 -40
- package/src/viteTarget.ts +323 -0
- package/editor/assets/browserAll-DCAvtrKG.js +0 -1
- package/editor/assets/init-ClFt_GA_.js +0 -1
- package/editor/assets/webworkerAll-Do_n5w1y.js +0 -1
- package/src/wechatTarget.ts +0 -578
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t,t as n,u as r}from"./index-DoPhYWF1.js";r.add(n,t),r.add(e);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./index-DoPhYWF1.js";import"./init-CuKNsO_k.js";
|
package/editor/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Pixifact Editor</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DoPhYWF1.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D3t5XxiB.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pixifact Scene 自动化 CLI。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"start": "bun src/pixifact-cli.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"
|
|
36
|
-
"
|
|
35
|
+
"pixifact": "^0.7.0",
|
|
36
|
+
"vite": "^8.0.10"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/automation.ts
CHANGED
|
@@ -465,7 +465,7 @@ function walkFiles(root: string, directory: string, depth: number, maxDepth: num
|
|
|
465
465
|
|
|
466
466
|
for (const entry of entries) {
|
|
467
467
|
const absolute = path.join(directory, entry.name);
|
|
468
|
-
const relative = path.relative(root, absolute);
|
|
468
|
+
const relative = toPosixPath(path.relative(root, absolute));
|
|
469
469
|
if (entry.isDirectory()) {
|
|
470
470
|
results.push({
|
|
471
471
|
path: relative,
|
package/src/editorServer.ts
CHANGED
|
@@ -274,7 +274,12 @@ export function watchEditorProject(projectRoot: string, onProjectFileChanged: (p
|
|
|
274
274
|
const changedPathTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
275
275
|
const watcher = fs.watch(projectRoot, { recursive: true }, (_event, fileName) => {
|
|
276
276
|
if (!fileName) return;
|
|
277
|
-
const
|
|
277
|
+
const filePath = String(fileName);
|
|
278
|
+
const absolutePath = path.resolve(projectRoot, filePath);
|
|
279
|
+
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const changedPath = filePath.split(path.sep).join('/');
|
|
278
283
|
const pending = changedPathTimers.get(changedPath);
|
|
279
284
|
if (pending) clearTimeout(pending);
|
|
280
285
|
changedPathTimers.set(changedPath, setTimeout(() => {
|
package/src/pixifact-cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
4
5
|
import { createPixifactAutomation } from './automation';
|
|
5
6
|
import { hintForCommandError } from 'pixifact';
|
|
6
7
|
import { CompileSceneError, compileScenes } from 'pixifact/compiler-node';
|
|
@@ -10,19 +11,19 @@ import { captureEditorScreenshot, queryEditorContext } from './editorSession';
|
|
|
10
11
|
import { queryRuntime, queryRuntimeList } from './runtimeSession';
|
|
11
12
|
import type { RuntimeLogLevel, RuntimeRequest } from 'pixifact/runtime-dev';
|
|
12
13
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
} from './
|
|
14
|
+
buildPixifactTarget,
|
|
15
|
+
devPixifactTarget,
|
|
16
|
+
validatePixifactTarget,
|
|
17
|
+
type PixifactDevEvent,
|
|
18
|
+
PixifactTargetError,
|
|
19
|
+
} from './viteTarget';
|
|
19
20
|
|
|
20
21
|
type Automation = ReturnType<typeof createPixifactAutomation>;
|
|
21
22
|
|
|
22
23
|
interface CliOptions {
|
|
23
24
|
automation?: Automation;
|
|
24
25
|
captureEditorScreenshot?: typeof captureEditorScreenshot;
|
|
25
|
-
|
|
26
|
+
onDevEvent?: (event: PixifactDevEvent) => void;
|
|
26
27
|
readEditorContext?: typeof queryEditorContext;
|
|
27
28
|
listRuntimes?: typeof queryRuntimeList;
|
|
28
29
|
queryRuntime?: typeof queryRuntime;
|
|
@@ -94,6 +95,17 @@ function optionalFlag(flags: Record<string, string | true>, name: string) {
|
|
|
94
95
|
return value === undefined ? undefined : requireFlag(flags, name);
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function assertAllowedFlags(
|
|
99
|
+
flags: Record<string, string | true>,
|
|
100
|
+
allowed: readonly string[],
|
|
101
|
+
command: string,
|
|
102
|
+
) {
|
|
103
|
+
const unexpected = Object.keys(flags).find((name) => !allowed.includes(name));
|
|
104
|
+
if (unexpected !== undefined) {
|
|
105
|
+
throw new Error(`Unknown option "--${unexpected}" for Pixifact ${command}.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
97
109
|
function finiteNumberFlag(flags: Record<string, string | true>, name: string) {
|
|
98
110
|
const value = Number(requireFlag(flags, name));
|
|
99
111
|
if (!Number.isFinite(value)) throw new Error(`--${name} must be a finite number.`);
|
|
@@ -256,7 +268,7 @@ async function executeFileCommand(
|
|
|
256
268
|
positionals: string[],
|
|
257
269
|
flags: Record<string, string | true>,
|
|
258
270
|
automation: Automation,
|
|
259
|
-
|
|
271
|
+
onDevEvent?: (event: PixifactDevEvent) => void,
|
|
260
272
|
startEditor: typeof startEditorServer = startEditorServer,
|
|
261
273
|
readEditorContext: typeof queryEditorContext = queryEditorContext,
|
|
262
274
|
captureScreenshot: typeof captureEditorScreenshot = captureEditorScreenshot,
|
|
@@ -371,68 +383,61 @@ async function executeFileCommand(
|
|
|
371
383
|
}
|
|
372
384
|
|
|
373
385
|
if ((area === 'build' || area === 'dev' || area === 'validate') && action === undefined) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
386
|
+
assertAllowedFlags(flags, ['mode', 'project-root'], area);
|
|
387
|
+
const mode = flags.mode === undefined
|
|
388
|
+
? area === 'dev' ? 'development' : 'production'
|
|
389
|
+
: requireFlag(flags, 'mode');
|
|
378
390
|
if (area === 'validate') {
|
|
379
|
-
const result = await
|
|
391
|
+
const result = await validatePixifactTarget(projectRoot, mode);
|
|
380
392
|
return result.diagnostics.length > 0
|
|
381
393
|
? {
|
|
382
394
|
ok: false,
|
|
383
|
-
|
|
395
|
+
mode,
|
|
396
|
+
platform: result.platform,
|
|
384
397
|
diagnostics: result.diagnostics,
|
|
385
|
-
hint: 'Fix the listed
|
|
398
|
+
hint: 'Fix the listed target diagnostics, then validate again.',
|
|
386
399
|
}
|
|
387
|
-
: { ok: true, projectRoot,
|
|
400
|
+
: { ok: true, projectRoot, mode, platform: result.platform };
|
|
388
401
|
}
|
|
389
402
|
if (area === 'dev') {
|
|
390
|
-
if (flags.mode !== undefined) {
|
|
391
|
-
throw new Error('dev --target wechat always uses development mode; remove --mode.');
|
|
392
|
-
}
|
|
393
403
|
try {
|
|
394
|
-
const session = await
|
|
404
|
+
const session = await devPixifactTarget(projectRoot, mode, onDevEvent);
|
|
395
405
|
return {
|
|
396
406
|
ok: true,
|
|
397
407
|
projectRoot,
|
|
398
|
-
|
|
399
|
-
mode
|
|
408
|
+
platform: session.platform,
|
|
409
|
+
mode,
|
|
400
410
|
watching: true,
|
|
401
411
|
report: session.initialReport,
|
|
402
412
|
};
|
|
403
413
|
} catch (error) {
|
|
404
|
-
if (error instanceof
|
|
414
|
+
if (error instanceof PixifactTargetError) {
|
|
405
415
|
return {
|
|
406
416
|
ok: false,
|
|
407
|
-
target,
|
|
408
417
|
error: error.message,
|
|
409
418
|
diagnostics: error.diagnostics,
|
|
410
|
-
hint: 'Fix the listed
|
|
419
|
+
hint: 'Fix the listed target diagnostics, then start dev again.',
|
|
411
420
|
};
|
|
412
421
|
}
|
|
413
422
|
throw error;
|
|
414
423
|
}
|
|
415
424
|
}
|
|
416
|
-
const mode = flags.mode === undefined ? 'production' : requireFlag(flags, 'mode');
|
|
417
|
-
if (mode !== 'development' && mode !== 'production') {
|
|
418
|
-
throw new Error('--mode must be development or production.');
|
|
419
|
-
}
|
|
420
425
|
try {
|
|
426
|
+
const report = await buildPixifactTarget(projectRoot, mode);
|
|
421
427
|
return {
|
|
422
428
|
ok: true,
|
|
423
429
|
projectRoot,
|
|
424
|
-
|
|
430
|
+
platform: report.platform,
|
|
425
431
|
mode,
|
|
426
|
-
report
|
|
432
|
+
report,
|
|
427
433
|
};
|
|
428
434
|
} catch (error) {
|
|
429
|
-
if (error instanceof
|
|
435
|
+
if (error instanceof PixifactTargetError) {
|
|
430
436
|
return {
|
|
431
437
|
ok: false,
|
|
432
|
-
target,
|
|
433
438
|
error: error.message,
|
|
434
439
|
diagnostics: error.diagnostics,
|
|
435
|
-
hint: 'Fix the listed
|
|
440
|
+
hint: 'Fix the listed target diagnostics, then build again.',
|
|
436
441
|
};
|
|
437
442
|
}
|
|
438
443
|
throw error;
|
|
@@ -510,9 +515,9 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
510
515
|
'scene inspect --scene <scene-path>',
|
|
511
516
|
'scene validate --scene <scene-path>',
|
|
512
517
|
'compile-scenes',
|
|
513
|
-
'validate --
|
|
514
|
-
'build --
|
|
515
|
-
'dev --
|
|
518
|
+
'validate [--mode <vite-mode>]',
|
|
519
|
+
'build [--mode <vite-mode>]',
|
|
520
|
+
'dev [--mode <vite-mode>]',
|
|
516
521
|
],
|
|
517
522
|
aiValidationAlternatives: [
|
|
518
523
|
'scene validate --all',
|
|
@@ -550,7 +555,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
550
555
|
parsed.positionals,
|
|
551
556
|
parsed.flags,
|
|
552
557
|
automation,
|
|
553
|
-
options.
|
|
558
|
+
options.onDevEvent,
|
|
554
559
|
options.startEditor,
|
|
555
560
|
options.readEditorContext,
|
|
556
561
|
options.captureEditorScreenshot,
|
|
@@ -582,9 +587,9 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
582
587
|
}
|
|
583
588
|
}
|
|
584
589
|
|
|
585
|
-
if (import.meta.url ===
|
|
590
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
586
591
|
const result = await executePixifactCli(process.argv.slice(2), {
|
|
587
|
-
|
|
592
|
+
onDevEvent: (event) => process.stdout.write(jsonLine(event)),
|
|
588
593
|
});
|
|
589
594
|
process.stdout.write(result.stdout);
|
|
590
595
|
process.stderr.write(result.stderr);
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { access, readFile, readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
build,
|
|
5
|
+
createServer,
|
|
6
|
+
resolveConfig,
|
|
7
|
+
type Rollup,
|
|
8
|
+
} from 'vite';
|
|
9
|
+
import {
|
|
10
|
+
parsePixifactProjectConfig,
|
|
11
|
+
pixifactProjectConfigFileName,
|
|
12
|
+
type PixifactProjectConfig,
|
|
13
|
+
} from 'pixifact';
|
|
14
|
+
import {
|
|
15
|
+
compilerSceneNodeLocator,
|
|
16
|
+
parseSceneTemplate,
|
|
17
|
+
type SceneTemplateNode,
|
|
18
|
+
} from 'pixifact/compiler';
|
|
19
|
+
import {
|
|
20
|
+
loadPixifactEnv,
|
|
21
|
+
pixifactBuildReporter,
|
|
22
|
+
type PixifactBuildReport,
|
|
23
|
+
type PixifactPlatform,
|
|
24
|
+
} from 'pixifact/compiler-node';
|
|
25
|
+
|
|
26
|
+
export interface PixifactTargetDiagnostic {
|
|
27
|
+
asset?: string;
|
|
28
|
+
message: string;
|
|
29
|
+
node?: string;
|
|
30
|
+
scene?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type PixifactDevEvent = {
|
|
34
|
+
type: 'built';
|
|
35
|
+
report: PixifactBuildReport;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'error';
|
|
38
|
+
error: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export interface PixifactDevSession {
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
initialReport: PixifactBuildReport;
|
|
44
|
+
platform: PixifactPlatform;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class PixifactTargetError extends Error {
|
|
48
|
+
constructor(
|
|
49
|
+
message: string,
|
|
50
|
+
readonly diagnostics: PixifactTargetDiagnostic[],
|
|
51
|
+
) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = 'PixifactTargetError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function validatePixifactTarget(projectRootInput: string, mode: string) {
|
|
58
|
+
const projectRoot = path.resolve(projectRootInput);
|
|
59
|
+
const { platform } = loadPixifactEnv(projectRoot, mode);
|
|
60
|
+
const config = parsePixifactProjectConfig(JSON.parse(await readFile(
|
|
61
|
+
path.join(projectRoot, pixifactProjectConfigFileName),
|
|
62
|
+
'utf8',
|
|
63
|
+
)));
|
|
64
|
+
const diagnostics = await collectDiagnostics(projectRoot, config, platform);
|
|
65
|
+
const resolved = await resolveConfig({ root: projectRoot, mode, logLevel: 'silent' }, 'build', mode);
|
|
66
|
+
const pluginNames = new Set(resolved.plugins.map((plugin) => plugin.name));
|
|
67
|
+
for (const name of [
|
|
68
|
+
'pixifact-scenes',
|
|
69
|
+
'pixifact-platform',
|
|
70
|
+
'pixifact-assets',
|
|
71
|
+
'pixifact-resources',
|
|
72
|
+
'pixifact-minigame',
|
|
73
|
+
]) {
|
|
74
|
+
if (!pluginNames.has(name)) {
|
|
75
|
+
diagnostics.push({ message: `vite.config.ts must include pixifact(); missing ${name}.` });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (platform !== 'web') {
|
|
80
|
+
const packageName = `@pixifact/platform-${platform}`;
|
|
81
|
+
const resolve = resolved.createResolver();
|
|
82
|
+
if (!await resolve(packageName, path.join(projectRoot, 'src/main.ts'))) {
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
message: `缺少 ${packageName};请运行 bun add ${packageName}。`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { config, diagnostics, mode, platform, projectRoot, resolved };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function buildPixifactTarget(
|
|
92
|
+
projectRootInput: string,
|
|
93
|
+
mode: string,
|
|
94
|
+
): Promise<PixifactBuildReport> {
|
|
95
|
+
const validation = await validatePixifactTarget(projectRootInput, mode);
|
|
96
|
+
if (validation.diagnostics.length > 0) {
|
|
97
|
+
throw new PixifactTargetError('Pixifact target validation failed.', validation.diagnostics);
|
|
98
|
+
}
|
|
99
|
+
let report: PixifactBuildReport | undefined;
|
|
100
|
+
await build({
|
|
101
|
+
root: validation.projectRoot,
|
|
102
|
+
mode,
|
|
103
|
+
logLevel: 'silent',
|
|
104
|
+
plugins: [pixifactBuildReporter(validation.platform, (value) => {
|
|
105
|
+
report = value;
|
|
106
|
+
})],
|
|
107
|
+
});
|
|
108
|
+
if (!report) throw new Error('Vite completed without a Pixifact build report.');
|
|
109
|
+
return report;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function devPixifactTarget(
|
|
113
|
+
projectRootInput: string,
|
|
114
|
+
mode: string,
|
|
115
|
+
onEvent: (event: PixifactDevEvent) => void = () => undefined,
|
|
116
|
+
): Promise<PixifactDevSession> {
|
|
117
|
+
const validation = await validatePixifactTarget(projectRootInput, mode);
|
|
118
|
+
if (validation.diagnostics.length > 0) {
|
|
119
|
+
throw new PixifactTargetError('Pixifact target validation failed.', validation.diagnostics);
|
|
120
|
+
}
|
|
121
|
+
if (validation.platform === 'web') {
|
|
122
|
+
const server = await createServer({
|
|
123
|
+
root: validation.projectRoot,
|
|
124
|
+
mode,
|
|
125
|
+
logLevel: 'silent',
|
|
126
|
+
});
|
|
127
|
+
await server.listen();
|
|
128
|
+
return {
|
|
129
|
+
close: () => server.close(),
|
|
130
|
+
initialReport: emptyReport('web', validation.resolved.build.outDir),
|
|
131
|
+
platform: 'web',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
let resolveInitial: (report: PixifactBuildReport) => void;
|
|
135
|
+
let rejectInitial: (error: Error) => void;
|
|
136
|
+
const initial = new Promise<PixifactBuildReport>((resolve, reject) => {
|
|
137
|
+
resolveInitial = resolve;
|
|
138
|
+
rejectInitial = reject;
|
|
139
|
+
});
|
|
140
|
+
let first = true;
|
|
141
|
+
let completedReport: PixifactBuildReport | undefined;
|
|
142
|
+
const watcher = await build({
|
|
143
|
+
root: validation.projectRoot,
|
|
144
|
+
mode,
|
|
145
|
+
logLevel: 'silent',
|
|
146
|
+
build: { watch: {} },
|
|
147
|
+
plugins: [pixifactBuildReporter(validation.platform, (report) => {
|
|
148
|
+
completedReport = report;
|
|
149
|
+
})],
|
|
150
|
+
}) as Rollup.RollupWatcher;
|
|
151
|
+
watcher.on('event', (event) => {
|
|
152
|
+
if (event.code === 'END' && completedReport) {
|
|
153
|
+
const report = completedReport;
|
|
154
|
+
completedReport = undefined;
|
|
155
|
+
if (first) {
|
|
156
|
+
first = false;
|
|
157
|
+
resolveInitial(report);
|
|
158
|
+
} else {
|
|
159
|
+
onEvent({ type: 'built', report });
|
|
160
|
+
}
|
|
161
|
+
} else if (event.code === 'ERROR') {
|
|
162
|
+
const error = event.error instanceof Error ? event.error : new Error(String(event.error));
|
|
163
|
+
if (first) {
|
|
164
|
+
first = false;
|
|
165
|
+
rejectInitial(error);
|
|
166
|
+
} else {
|
|
167
|
+
onEvent({ type: 'error', error: error.message });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
close: () => watcher.close(),
|
|
173
|
+
initialReport: await initial,
|
|
174
|
+
platform: validation.platform,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function collectDiagnostics(
|
|
179
|
+
projectRoot: string,
|
|
180
|
+
config: PixifactProjectConfig,
|
|
181
|
+
platform: PixifactPlatform,
|
|
182
|
+
) {
|
|
183
|
+
const diagnostics: PixifactTargetDiagnostic[] = [];
|
|
184
|
+
await requireFile(projectRoot, 'src/main.ts', diagnostics, '应用入口 src/main.ts 不存在。');
|
|
185
|
+
if (platform !== 'web') {
|
|
186
|
+
await requireFile(projectRoot, `platforms/${platform}/game.json`, diagnostics, `${platform} game.json 不存在。`);
|
|
187
|
+
await requireFile(
|
|
188
|
+
projectRoot,
|
|
189
|
+
`platforms/${platform}/project.config.json`,
|
|
190
|
+
diagnostics,
|
|
191
|
+
`${platform} project.config.json 不存在。`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
for (const pack of config.resourcePacks ?? []) {
|
|
195
|
+
const root = path.join(projectRoot, 'resources', pack);
|
|
196
|
+
if (!await isDirectory(root)) {
|
|
197
|
+
diagnostics.push({ asset: `resources/${pack}`, message: `资源包 ${pack} 的目录不存在。` });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (platform !== 'web') {
|
|
201
|
+
for (const file of await collectFiles(root)) {
|
|
202
|
+
if (path.extname(file).toLowerCase() === '.svg') {
|
|
203
|
+
diagnostics.push({
|
|
204
|
+
asset: projectPath(projectRoot, file),
|
|
205
|
+
message: `${platform} 小游戏不支持 SVG 资源。`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const scene of Object.values(config.scenes)) {
|
|
212
|
+
if (!await exists(path.join(projectRoot, scene))) {
|
|
213
|
+
diagnostics.push({ asset: scene, message: 'Scene 文件不存在。' });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (platform === 'web') continue;
|
|
217
|
+
const template = parseSceneTemplate(await readFile(path.join(projectRoot, scene), 'utf8'));
|
|
218
|
+
await collectSceneDiagnostics(template.children, scene, '', projectRoot, platform, diagnostics);
|
|
219
|
+
}
|
|
220
|
+
if (platform !== 'web') {
|
|
221
|
+
const gameJsonPath = path.join(projectRoot, 'platforms', platform, 'game.json');
|
|
222
|
+
if (await exists(gameJsonPath)) {
|
|
223
|
+
const gameJson = JSON.parse(await readFile(gameJsonPath, 'utf8')) as Record<string, unknown>;
|
|
224
|
+
const property = platform === 'wechat' ? 'subpackages' : 'subPackages';
|
|
225
|
+
if (gameJson[property] !== undefined) {
|
|
226
|
+
diagnostics.push({
|
|
227
|
+
asset: `platforms/${platform}/game.json`,
|
|
228
|
+
message: `${property} 由 Pixifact 根据 resourcePacks 生成,不能在源模板中重复声明。`,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return diagnostics;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function collectSceneDiagnostics(
|
|
237
|
+
nodes: SceneTemplateNode[],
|
|
238
|
+
scene: string,
|
|
239
|
+
parentPath: string,
|
|
240
|
+
projectRoot: string,
|
|
241
|
+
platform: Exclude<PixifactPlatform, 'web'>,
|
|
242
|
+
diagnostics: PixifactTargetDiagnostic[],
|
|
243
|
+
) {
|
|
244
|
+
for (const [index, node] of nodes.entries()) {
|
|
245
|
+
const nodePath = parentPath ? `${parentPath}/${index}` : String(index);
|
|
246
|
+
if (node.kind === 'slotOutlet') continue;
|
|
247
|
+
if (node.kind === 'sceneInstance') {
|
|
248
|
+
for (const [slotName, children] of Object.entries(node.slots)) {
|
|
249
|
+
await collectSceneDiagnostics(children, scene, `${nodePath}/slot:${slotName}`, projectRoot, platform, diagnostics);
|
|
250
|
+
}
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const locator = compilerSceneNodeLocator(node, nodePath);
|
|
254
|
+
if (node.type === 'HTMLText' || node.type === 'DOMContainer') {
|
|
255
|
+
diagnostics.push({ message: `${platform} 小游戏不支持 ${node.type}。`, node: locator, scene });
|
|
256
|
+
}
|
|
257
|
+
if (typeof node.props.texture === 'string') {
|
|
258
|
+
const texture = node.props.texture;
|
|
259
|
+
const extension = path.extname(texture).toLowerCase();
|
|
260
|
+
if (!isProjectAssetPath(texture)) {
|
|
261
|
+
diagnostics.push({ asset: texture, message: `${platform} 小游戏 Scene 纹理必须使用项目相对路径。`, node: locator, scene });
|
|
262
|
+
} else if (!['.jpeg', '.jpg', '.png', '.webp'].includes(extension)) {
|
|
263
|
+
diagnostics.push({ asset: texture, message: `${platform} 小游戏 Scene 纹理不支持 ${extension || '无扩展名'} 格式。`, node: locator, scene });
|
|
264
|
+
} else if (!await exists(path.join(projectRoot, texture))) {
|
|
265
|
+
diagnostics.push({ asset: texture, message: `${platform} 小游戏 Scene 纹理文件不存在。`, node: locator, scene });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
await collectSceneDiagnostics(node.children, scene, nodePath, projectRoot, platform, diagnostics);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isProjectAssetPath(value: string) {
|
|
273
|
+
return value.trim() !== ''
|
|
274
|
+
&& !value.startsWith('/')
|
|
275
|
+
&& !value.startsWith('./')
|
|
276
|
+
&& !value.includes('\\')
|
|
277
|
+
&& !value.split('/').includes('..')
|
|
278
|
+
&& !/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function requireFile(
|
|
282
|
+
projectRoot: string,
|
|
283
|
+
relativePath: string,
|
|
284
|
+
diagnostics: PixifactTargetDiagnostic[],
|
|
285
|
+
message: string,
|
|
286
|
+
) {
|
|
287
|
+
if (!await exists(path.join(projectRoot, relativePath))) diagnostics.push({ asset: relativePath, message });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function collectFiles(root: string): Promise<string[]> {
|
|
291
|
+
const files: string[] = [];
|
|
292
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
293
|
+
const absolute = path.join(root, entry.name);
|
|
294
|
+
if (entry.isDirectory()) files.push(...await collectFiles(absolute));
|
|
295
|
+
else if (entry.isFile()) files.push(absolute);
|
|
296
|
+
}
|
|
297
|
+
return files;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function exists(filePath: string) {
|
|
301
|
+
try {
|
|
302
|
+
await access(filePath);
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function isDirectory(filePath: string) {
|
|
310
|
+
try {
|
|
311
|
+
return (await stat(filePath)).isDirectory();
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function emptyReport(platform: PixifactPlatform, outputDirectory: string): PixifactBuildReport {
|
|
318
|
+
return { mainPackageBytes: 0, outputDirectory, platform, subpackages: {}, totalPackageBytes: 0 };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function projectPath(root: string, filePath: string) {
|
|
322
|
+
return path.relative(root, filePath).replaceAll(path.sep, '/');
|
|
323
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,l as r,o as i,s as a,u as o}from"./index-D8cbY_GQ.js";import"./init-ClFt_GA_.js";o.add(t),o.mixin(r,a),o.add(i),o.add(e),o.mixin(r,n);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,r as t,t as n,u as r}from"./index-D8cbY_GQ.js";r.add(n,t),r.add(e);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./index-D8cbY_GQ.js";import"./init-ClFt_GA_.js";
|