@coze-arch/cli 0.0.35 → 0.0.36-alpha.0a210b
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/README.md +11 -0
- package/docs/deploy.md +164 -0
- package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +73 -18
- package/lib/__templates__/nextjs/scripts/dev.sh +10 -1
- package/lib/__templates__/nextjs/template.config.js +3 -1
- package/lib/__templates__/phaser/.coze +13 -0
- package/lib/__templates__/phaser/README.md +37 -0
- package/lib/__templates__/phaser/_gitignore +23 -0
- package/lib/__templates__/phaser/_npmrc +14 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
- package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/manifest.json +11 -0
- package/lib/__templates__/phaser/debug-runtime/flow-graph.json +20 -0
- package/lib/__templates__/phaser/index.html +12 -0
- package/lib/__templates__/phaser/package.json +31 -0
- package/lib/__templates__/phaser/pnpm-lock.yaml +661 -0
- package/lib/__templates__/phaser/scripts/build.sh +8 -0
- package/lib/__templates__/phaser/scripts/dev.sh +82 -0
- package/lib/__templates__/phaser/scripts/prepare.sh +7 -0
- package/lib/__templates__/phaser/scripts/spawn-detached.cjs +29 -0
- package/lib/__templates__/phaser/scripts/start.sh +82 -0
- package/lib/__templates__/phaser/scripts/validate.sh +7 -0
- package/lib/__templates__/phaser/src/assets.ts +15 -0
- package/lib/__templates__/phaser/src/global.d.ts +36 -0
- package/lib/__templates__/phaser/src/main.ts +28 -0
- package/lib/__templates__/phaser/src/scene/BootScene.ts +209 -0
- package/lib/__templates__/phaser/src/scene/DevLoadingScene.ts +66 -0
- package/lib/__templates__/phaser/src/style.css +28 -0
- package/lib/__templates__/phaser/src/types.ts +32 -0
- package/lib/__templates__/phaser/src/utils/asset-loader.ts +319 -0
- package/lib/__templates__/phaser/src/utils/index.ts +2 -0
- package/lib/__templates__/phaser/src/utils/mark-editable.ts +6 -0
- package/lib/__templates__/phaser/template.config.js +43 -0
- package/lib/__templates__/phaser/tsconfig.json +23 -0
- package/lib/__templates__/phaser/vite.config.ts +96 -0
- package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +20 -14
- package/lib/__templates__/templates.json +25 -0
- package/lib/__templates__/vite/template.config.js +3 -1
- package/lib/cli.js +3176 -201
- package/lib/deploy/package-project.js +226 -0
- package/lib/deploy/package-project.py +168 -0
- package/package.json +5 -4
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type Phaser from 'phaser';
|
|
2
|
+
|
|
3
|
+
import { AUDIOS, IMAGES, IMAGE_SEQUENCES, VIDEOS } from '../assets';
|
|
4
|
+
import type {
|
|
5
|
+
AssetCollections,
|
|
6
|
+
AssetSources,
|
|
7
|
+
ImageSequenceManifest,
|
|
8
|
+
LoadedAsset,
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
const FRAME_KEY_MARKER = ':frame:';
|
|
12
|
+
const MANIFEST_KEY_SUFFIX = ':manifest';
|
|
13
|
+
|
|
14
|
+
const getFrameKey = (assetKey: string, frameIndex: number): string =>
|
|
15
|
+
`${assetKey}${FRAME_KEY_MARKER}${String(frameIndex + 1).padStart(4, '0')}`;
|
|
16
|
+
|
|
17
|
+
const getManifestKey = (assetKey: string): string =>
|
|
18
|
+
`${assetKey}${MANIFEST_KEY_SUFFIX}`;
|
|
19
|
+
|
|
20
|
+
const getFrameSource = (directory: string, frameIndex: number): string =>
|
|
21
|
+
`${directory}/frame_${String(frameIndex + 1).padStart(4, '0')}.png`;
|
|
22
|
+
|
|
23
|
+
export const resolveAssetUrl = (source: string): string =>
|
|
24
|
+
new URL(source, document.baseURI).href;
|
|
25
|
+
|
|
26
|
+
const isImageSequenceManifest = (
|
|
27
|
+
value: unknown,
|
|
28
|
+
): value is ImageSequenceManifest => {
|
|
29
|
+
if (!value || typeof value !== 'object') {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const manifest = value as Partial<ImageSequenceManifest>;
|
|
34
|
+
return (
|
|
35
|
+
manifest.type === 'image_sequence' &&
|
|
36
|
+
typeof manifest.name === 'string' &&
|
|
37
|
+
typeof manifest.properties?.fps === 'number' &&
|
|
38
|
+
typeof manifest.properties.frame_count === 'number' &&
|
|
39
|
+
manifest.properties.frame_count > 0
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getImageSequenceManifest = (
|
|
44
|
+
scene: Phaser.Scene,
|
|
45
|
+
assetKey: string,
|
|
46
|
+
): ImageSequenceManifest => {
|
|
47
|
+
const manifest: unknown = scene.cache.json.get(getManifestKey(assetKey));
|
|
48
|
+
if (!isImageSequenceManifest(manifest)) {
|
|
49
|
+
throw new Error(`Invalid image sequence manifest: ${assetKey}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return manifest;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const queueImageSequenceFrames = (
|
|
56
|
+
scene: Phaser.Scene,
|
|
57
|
+
assetKey: string,
|
|
58
|
+
directory: string,
|
|
59
|
+
manifest: ImageSequenceManifest,
|
|
60
|
+
): void => {
|
|
61
|
+
Array.from(
|
|
62
|
+
{ length: manifest.properties.frame_count },
|
|
63
|
+
(_value, frameIndex) => {
|
|
64
|
+
scene.load.image(
|
|
65
|
+
getFrameKey(assetKey, frameIndex),
|
|
66
|
+
resolveAssetUrl(getFrameSource(directory, frameIndex)),
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const queueImages = (scene: Phaser.Scene, images: AssetSources): number => {
|
|
73
|
+
Object.entries(images).forEach(([assetKey, source]) => {
|
|
74
|
+
scene.load.image(assetKey, resolveAssetUrl(source));
|
|
75
|
+
});
|
|
76
|
+
return Object.keys(images).length;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const queueVideos = (scene: Phaser.Scene, videos: AssetSources): number => {
|
|
80
|
+
Object.entries(videos).forEach(([assetKey, source]) => {
|
|
81
|
+
scene.load.video(assetKey, resolveAssetUrl(source));
|
|
82
|
+
});
|
|
83
|
+
return Object.keys(videos).length;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const queueAudios = (scene: Phaser.Scene, audios: AssetSources): number => {
|
|
87
|
+
Object.entries(audios).forEach(([assetKey, source]) => {
|
|
88
|
+
scene.load.audio(assetKey, resolveAssetUrl(source));
|
|
89
|
+
});
|
|
90
|
+
return Object.keys(audios).length;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const queueImageSequences = (
|
|
94
|
+
scene: Phaser.Scene,
|
|
95
|
+
imageSequences: AssetSources,
|
|
96
|
+
): number => {
|
|
97
|
+
return Object.entries(imageSequences).reduce(
|
|
98
|
+
(fileCount, [assetKey, directory]) => {
|
|
99
|
+
const manifestKey = getManifestKey(assetKey);
|
|
100
|
+
const cachedManifest: unknown = scene.cache.json.get(manifestKey);
|
|
101
|
+
|
|
102
|
+
if (isImageSequenceManifest(cachedManifest)) {
|
|
103
|
+
queueImageSequenceFrames(
|
|
104
|
+
scene,
|
|
105
|
+
assetKey,
|
|
106
|
+
directory,
|
|
107
|
+
cachedManifest,
|
|
108
|
+
);
|
|
109
|
+
return fileCount + cachedManifest.properties.frame_count;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
scene.load.once(
|
|
113
|
+
`filecomplete-json-${manifestKey}`,
|
|
114
|
+
(_key: string, _type: string, data: unknown) => {
|
|
115
|
+
if (!isImageSequenceManifest(data)) {
|
|
116
|
+
throw new Error(`Invalid image sequence manifest: ${assetKey}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
queueImageSequenceFrames(scene, assetKey, directory, data);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
scene.load.json(
|
|
124
|
+
manifestKey,
|
|
125
|
+
resolveAssetUrl(`${directory}/manifest.json`),
|
|
126
|
+
);
|
|
127
|
+
return fileCount + 1;
|
|
128
|
+
},
|
|
129
|
+
0,
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const loadAssets = (
|
|
134
|
+
scene: Phaser.Scene,
|
|
135
|
+
collections: AssetCollections,
|
|
136
|
+
): number =>
|
|
137
|
+
queueImages(scene, collections.images ?? {}) +
|
|
138
|
+
queueImageSequences(scene, collections.imageSequences ?? {}) +
|
|
139
|
+
queueVideos(scene, collections.videos ?? {}) +
|
|
140
|
+
queueAudios(scene, collections.audios ?? {});
|
|
141
|
+
|
|
142
|
+
export const getImageSequenceRuntimeKeys = (
|
|
143
|
+
assetKey: string,
|
|
144
|
+
): { texture: string; animation: string } => ({
|
|
145
|
+
texture: getFrameKey(assetKey, 0),
|
|
146
|
+
animation: `${assetKey}:animation`,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export const getAssetKeyFromFileKey = (fileKey: string): string =>
|
|
150
|
+
fileKey.split(FRAME_KEY_MARKER)[0]?.replace(MANIFEST_KEY_SUFFIX, '') ??
|
|
151
|
+
fileKey;
|
|
152
|
+
|
|
153
|
+
export const getAssetLabelFromFileKey = (
|
|
154
|
+
fileKey: string,
|
|
155
|
+
collections: AssetCollections,
|
|
156
|
+
): string => {
|
|
157
|
+
const assetKey = getAssetKeyFromFileKey(fileKey);
|
|
158
|
+
|
|
159
|
+
if (assetKey in (collections.images ?? {})) {
|
|
160
|
+
return `图片 ${assetKey}`;
|
|
161
|
+
}
|
|
162
|
+
if (assetKey in (collections.imageSequences ?? {})) {
|
|
163
|
+
return `帧动画 ${assetKey}`;
|
|
164
|
+
}
|
|
165
|
+
if (assetKey in (collections.videos ?? {})) {
|
|
166
|
+
return `视频 ${assetKey}`;
|
|
167
|
+
}
|
|
168
|
+
if (assetKey in (collections.audios ?? {})) {
|
|
169
|
+
return `音频 ${assetKey}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return assetKey;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const registerImageSequenceAnimations = (
|
|
176
|
+
scene: Phaser.Scene,
|
|
177
|
+
imageSequences: AssetSources,
|
|
178
|
+
): void => {
|
|
179
|
+
Object.keys(imageSequences).forEach(assetKey => {
|
|
180
|
+
const manifest = getImageSequenceManifest(scene, assetKey);
|
|
181
|
+
const animationKey = getImageSequenceRuntimeKeys(assetKey).animation;
|
|
182
|
+
if (scene.anims.exists(animationKey)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
scene.anims.create({
|
|
187
|
+
key: animationKey,
|
|
188
|
+
frames: Array.from(
|
|
189
|
+
{ length: manifest.properties.frame_count },
|
|
190
|
+
(_value, frameIndex) => ({ key: getFrameKey(assetKey, frameIndex) }),
|
|
191
|
+
),
|
|
192
|
+
frameRate: manifest.properties.fps,
|
|
193
|
+
repeat: -1,
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const findAsset = (
|
|
199
|
+
assetKey: string,
|
|
200
|
+
): { collections: AssetCollections; loadedAsset: LoadedAsset } => {
|
|
201
|
+
const matches: Array<{
|
|
202
|
+
collections: AssetCollections;
|
|
203
|
+
loadedAsset: LoadedAsset;
|
|
204
|
+
}> = [];
|
|
205
|
+
|
|
206
|
+
if (Object.hasOwn(IMAGES, assetKey)) {
|
|
207
|
+
matches.push({
|
|
208
|
+
collections: { images: { [assetKey]: IMAGES[assetKey] } },
|
|
209
|
+
loadedAsset: { kind: 'image', key: assetKey },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (Object.hasOwn(IMAGE_SEQUENCES, assetKey)) {
|
|
213
|
+
matches.push({
|
|
214
|
+
collections: {
|
|
215
|
+
imageSequences: { [assetKey]: IMAGE_SEQUENCES[assetKey] },
|
|
216
|
+
},
|
|
217
|
+
loadedAsset: {
|
|
218
|
+
kind: 'image-sequence',
|
|
219
|
+
key: assetKey,
|
|
220
|
+
...getImageSequenceRuntimeKeys(assetKey),
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (Object.hasOwn(VIDEOS, assetKey)) {
|
|
225
|
+
matches.push({
|
|
226
|
+
collections: { videos: { [assetKey]: VIDEOS[assetKey] } },
|
|
227
|
+
loadedAsset: { kind: 'video', key: assetKey },
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (Object.hasOwn(AUDIOS, assetKey)) {
|
|
231
|
+
matches.push({
|
|
232
|
+
collections: { audios: { [assetKey]: AUDIOS[assetKey] } },
|
|
233
|
+
loadedAsset: { kind: 'audio', key: assetKey },
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (matches.length === 0) {
|
|
238
|
+
throw new Error(`Asset not found: ${assetKey}`);
|
|
239
|
+
}
|
|
240
|
+
if (matches.length > 1) {
|
|
241
|
+
throw new Error(`Duplicate asset key: ${assetKey}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return matches[0];
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const isAssetLoaded = (
|
|
248
|
+
scene: Phaser.Scene,
|
|
249
|
+
loadedAsset: LoadedAsset,
|
|
250
|
+
): boolean => {
|
|
251
|
+
switch (loadedAsset.kind) {
|
|
252
|
+
case 'image':
|
|
253
|
+
return scene.textures.exists(loadedAsset.key);
|
|
254
|
+
case 'image-sequence':
|
|
255
|
+
return scene.anims.exists(loadedAsset.animation);
|
|
256
|
+
case 'video':
|
|
257
|
+
return scene.cache.video.exists(loadedAsset.key);
|
|
258
|
+
case 'audio':
|
|
259
|
+
return scene.cache.audio.exists(loadedAsset.key);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
interface LoaderFileLike {
|
|
264
|
+
key: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export const loadAsset = async (
|
|
268
|
+
scene: Phaser.Scene,
|
|
269
|
+
assetKey: string,
|
|
270
|
+
): Promise<LoadedAsset> => {
|
|
271
|
+
const { collections, loadedAsset } = findAsset(assetKey);
|
|
272
|
+
if (isAssetLoaded(scene, loadedAsset)) {
|
|
273
|
+
return loadedAsset;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return new Promise<LoadedAsset>((resolve, reject) => {
|
|
277
|
+
const cleanup = (): void => {
|
|
278
|
+
scene.load.off('complete', onComplete);
|
|
279
|
+
scene.load.off('loaderror', onLoadError);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const onComplete = (): void => {
|
|
283
|
+
cleanup();
|
|
284
|
+
try {
|
|
285
|
+
if (loadedAsset.kind === 'image-sequence') {
|
|
286
|
+
registerImageSequenceAnimations(
|
|
287
|
+
scene,
|
|
288
|
+
collections.imageSequences ?? {},
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
resolve(loadedAsset);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
reject(error);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const onLoadError = (file: LoaderFileLike): void => {
|
|
298
|
+
if (getAssetKeyFromFileKey(file.key) !== assetKey) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
cleanup();
|
|
303
|
+
reject(new Error(`Failed to load asset: ${assetKey}`));
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
scene.load.once('complete', onComplete);
|
|
307
|
+
scene.load.on('loaderror', onLoadError);
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
loadAssets(scene, collections);
|
|
311
|
+
if (!scene.load.isLoading()) {
|
|
312
|
+
scene.load.start();
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
cleanup();
|
|
316
|
+
reject(error);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const paramsSchema = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
appName: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
minLength: 1,
|
|
16
|
+
pattern: '^[a-z0-9-]+$',
|
|
17
|
+
description:
|
|
18
|
+
'Application name (lowercase, alphanumeric and hyphens only)',
|
|
19
|
+
},
|
|
20
|
+
port: {
|
|
21
|
+
type: 'number',
|
|
22
|
+
default: 5000,
|
|
23
|
+
minimum: 1024,
|
|
24
|
+
maximum: 65535,
|
|
25
|
+
description: 'Development and preview server port',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: [],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const config = {
|
|
33
|
+
description:
|
|
34
|
+
'Phaser(2D 游戏):`coze-dev init ${COZE_WORKSPACE_PATH} --template phaser`\n' +
|
|
35
|
+
'- 适用:基于 Phaser 3、Vite 和 TypeScript 的 2D Web 游戏。',
|
|
36
|
+
paramsSchema,
|
|
37
|
+
defaultParams: {
|
|
38
|
+
appName: 'phaser-game',
|
|
39
|
+
port: 5000,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default config;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"baseUrl": ".",
|
|
9
|
+
"paths": {
|
|
10
|
+
"@/*": ["src/*"]
|
|
11
|
+
},
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"esModuleInterop": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"types": ["node"]
|
|
21
|
+
},
|
|
22
|
+
"include": ["src", "vite.config.ts"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { cp } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { defineConfig, type Plugin } from "vite";
|
|
4
|
+
import serveStatic from "serve-static";
|
|
5
|
+
|
|
6
|
+
const DEV_PHASER_BRIDGE_URL =
|
|
7
|
+
"https://unpkg.byted-static.com/latest/coze-game/phaser-bridge/dist/index.js";
|
|
8
|
+
|
|
9
|
+
function injectDevPhaserBridge(): Plugin {
|
|
10
|
+
return {
|
|
11
|
+
name: "phaser-dev-bridge",
|
|
12
|
+
transformIndexHtml() {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
tag: "script",
|
|
16
|
+
attrs: { src: DEV_PHASER_BRIDGE_URL },
|
|
17
|
+
injectTo: "head",
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mountDevAssets(directory: string): Plugin {
|
|
25
|
+
const serveAssets = serveStatic(directory, {
|
|
26
|
+
cacheControl: false,
|
|
27
|
+
dotfiles: "allow",
|
|
28
|
+
fallthrough: false,
|
|
29
|
+
index: false,
|
|
30
|
+
redirect: false,
|
|
31
|
+
setHeaders(response) {
|
|
32
|
+
response.setHeader("Cache-Control", "no-cache");
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
name: "phaser-dev-assets",
|
|
38
|
+
configureServer(server) {
|
|
39
|
+
server.middlewares.use("/assets", (request, response, next) => {
|
|
40
|
+
const isRead = request.method === "GET" || request.method === "HEAD";
|
|
41
|
+
if (!isRead || request.url?.includes("?")) {
|
|
42
|
+
response.statusCode = 404;
|
|
43
|
+
response.end("Not Found\n");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
serveAssets(request, response, next);
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function copyGameAssets(directory: string): Plugin {
|
|
53
|
+
return {
|
|
54
|
+
name: "phaser-build-assets",
|
|
55
|
+
async writeBundle(outputOptions) {
|
|
56
|
+
const outputDirectory = path.resolve(
|
|
57
|
+
import.meta.dirname,
|
|
58
|
+
outputOptions.dir ?? "dist",
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await cp(directory, path.join(outputDirectory, "assets"), {
|
|
62
|
+
recursive: true,
|
|
63
|
+
force: true,
|
|
64
|
+
filter(source) {
|
|
65
|
+
return path.basename(source) !== ".DS_Store";
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default defineConfig(() => {
|
|
73
|
+
const isDev = process.env.COZE_PHASER_GAME_ENV === "DEV";
|
|
74
|
+
const assetsDirectory = path.join(import.meta.dirname, "assets");
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
resolve: {
|
|
78
|
+
alias: {
|
|
79
|
+
"@": path.resolve(import.meta.dirname, "src"),
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
server: {
|
|
83
|
+
// 游戏需用户手动刷新,防止游玩过程中丢失游戏状态
|
|
84
|
+
hmr: false,
|
|
85
|
+
},
|
|
86
|
+
plugins: [
|
|
87
|
+
copyGameAssets(assetsDirectory),
|
|
88
|
+
...(isDev
|
|
89
|
+
? [
|
|
90
|
+
injectDevPhaserBridge(),
|
|
91
|
+
mountDevAssets(assetsDirectory),
|
|
92
|
+
]
|
|
93
|
+
: []),
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
@@ -8,6 +8,9 @@ cd "${COZE_WORKSPACE_PATH}"
|
|
|
8
8
|
# PID 文件,用于追踪上一次启动的进程树
|
|
9
9
|
# ---------------------------------------------------------
|
|
10
10
|
PID_FILE="/tmp/coze-dev-run.pid"
|
|
11
|
+
LOG_DIR="/tmp/coze-logs"
|
|
12
|
+
LOG_FILE="${LOG_DIR}/dev.log"
|
|
13
|
+
DEV_PID=""
|
|
11
14
|
|
|
12
15
|
# ---------------------------------------------------------
|
|
13
16
|
# 工具函数
|
|
@@ -89,7 +92,8 @@ cleanup_previous_run() {
|
|
|
89
92
|
# 2. 安装依赖
|
|
90
93
|
# ---------------------------------------------------------
|
|
91
94
|
echo "📦 Installing dependencies..."
|
|
92
|
-
pnpm
|
|
95
|
+
PNPM_BIN="$(command -v pnpm)"
|
|
96
|
+
"${PNPM_BIN}" install
|
|
93
97
|
echo "✅ Dependencies installed successfully!"
|
|
94
98
|
|
|
95
99
|
# ---------------------------------------------------------
|
|
@@ -108,11 +112,14 @@ kill_port_if_listening "${SERVER_PORT}"
|
|
|
108
112
|
# 4. 退出时自动清理子进程(信号 trap)
|
|
109
113
|
# ---------------------------------------------------------
|
|
110
114
|
cleanup_on_exit() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
kill
|
|
115
|
+
local exit_code=$?
|
|
116
|
+
trap - EXIT INT TERM HUP
|
|
117
|
+
if [[ -n "${DEV_PID}" ]] && kill -0 "${DEV_PID}" 2>/dev/null; then
|
|
118
|
+
echo "🛑 dev_run.sh exiting, cleaning up child processes..."
|
|
119
|
+
kill_process_tree "${DEV_PID}"
|
|
120
|
+
fi
|
|
114
121
|
rm -f "${PID_FILE}"
|
|
115
|
-
exit
|
|
122
|
+
exit "${exit_code}"
|
|
116
123
|
}
|
|
117
124
|
trap cleanup_on_exit EXIT INT TERM HUP
|
|
118
125
|
|
|
@@ -134,17 +141,16 @@ start_service() {
|
|
|
134
141
|
echo "Starting Taro H5 Dev Server and NestJS Server..."
|
|
135
142
|
|
|
136
143
|
export PORT=${DEPLOY_RUN_PORT}
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
mkdir -p "${LOG_DIR}"
|
|
145
|
+
: > "${LOG_FILE}"
|
|
139
146
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
echo "${
|
|
144
|
-
echo "📝 Dev process started with PID: ${
|
|
147
|
+
# 后台启动以便记录真实 pnpm PID,但脚本持续 wait,保持外层 CLI 生命周期有效。
|
|
148
|
+
"${PNPM_BIN}" dev > >(tee -a "${LOG_FILE}") 2>&1 &
|
|
149
|
+
DEV_PID=$!
|
|
150
|
+
echo "${DEV_PID}" > "${PID_FILE}"
|
|
151
|
+
echo "📝 Dev process started with PID: ${DEV_PID} (saved to ${PID_FILE})"
|
|
145
152
|
|
|
146
|
-
|
|
147
|
-
wait "${dev_pid}" || true
|
|
153
|
+
wait "${DEV_PID}"
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
echo "Starting HTTP services on port ${DEPLOY_RUN_PORT} (web) and ${SERVER_PORT} (server)..."
|
|
@@ -94,6 +94,31 @@
|
|
|
94
94
|
"additionalProperties": false
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
|
+
{
|
|
98
|
+
"name": "phaser",
|
|
99
|
+
"description": "Phaser(2D 游戏):`coze-dev init ${COZE_WORKSPACE_PATH} --template phaser`\n- 适用:基于 Phaser 3、Vite 和 TypeScript 的 2D Web 游戏。",
|
|
100
|
+
"location": "./phaser",
|
|
101
|
+
"paramsSchema": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"appName": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1,
|
|
107
|
+
"pattern": "^[a-z0-9-]+$",
|
|
108
|
+
"description": "Application name (lowercase, alphanumeric and hyphens only)"
|
|
109
|
+
},
|
|
110
|
+
"port": {
|
|
111
|
+
"type": "number",
|
|
112
|
+
"default": 5000,
|
|
113
|
+
"minimum": 1024,
|
|
114
|
+
"maximum": 65535,
|
|
115
|
+
"description": "Development and preview server port"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"required": [],
|
|
119
|
+
"additionalProperties": false
|
|
120
|
+
}
|
|
121
|
+
},
|
|
97
122
|
{
|
|
98
123
|
"name": "pi-agent",
|
|
99
124
|
"description": "Pi Agent:`coze-dev init ${COZE_WORKSPACE_PATH} --template pi-agent`\n- 适用:基于 pi-agent-core 的 AI Agent 应用\n- 支持飞书、微信等多渠道接入\n- 内置 Dashboard 管理面板\n- 使用 TypeScript + Express + Vite",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import { resolve, join, basename } from 'path';
|
|
4
4
|
import { appendFileSync, openSync, closeSync, mkdirSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
@@ -81,7 +82,8 @@ const config = {
|
|
|
81
82
|
const projectRoot = resolve(outputPath);
|
|
82
83
|
|
|
83
84
|
// Determine log directory
|
|
84
|
-
const
|
|
85
|
+
const cozeHome = process.env.COZE_HOME || join(homedir(), '.coze');
|
|
86
|
+
const logDir = process.env.COZE_LOG_DIR || join(cozeHome, 'logs');
|
|
85
87
|
mkdirSync(logDir, { recursive: true });
|
|
86
88
|
|
|
87
89
|
// Use project name in log file to avoid conflicts
|