@coze-arch/cli 0.0.37 → 0.1.2

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.
Files changed (155) hide show
  1. package/README.md +69 -104
  2. package/docs/deploy.md +181 -0
  3. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +4 -0
  4. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +108 -19
  5. package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +1 -1
  6. package/lib/__templates__/expo/.cozeproj/scripts/server_dev_run.sh +1 -1
  7. package/lib/__templates__/expo/client/metro.config.js +3 -1
  8. package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
  9. package/lib/__templates__/expo/server/package.json +2 -2
  10. package/lib/__templates__/expo/server/src/index.ts +1 -1
  11. package/lib/__templates__/expo/template.config.js +11 -2
  12. package/lib/__templates__/native-static/.coze +4 -2
  13. package/lib/__templates__/native-static/scripts/serve.ps1 +7 -0
  14. package/lib/__templates__/nextjs/.coze +5 -0
  15. package/lib/__templates__/nextjs/package.json +2 -2
  16. package/lib/__templates__/nextjs/pnpm-lock.yaml +5 -5
  17. package/lib/__templates__/nextjs/scripts/build.ps1 +16 -0
  18. package/lib/__templates__/nextjs/scripts/dev.ps1 +63 -0
  19. package/lib/__templates__/nextjs/scripts/dev.sh +234 -7
  20. package/lib/__templates__/nextjs/scripts/prepare.ps1 +18 -0
  21. package/lib/__templates__/nextjs/scripts/start.ps1 +11 -0
  22. package/lib/__templates__/nextjs/scripts/validate.ps1 +8 -0
  23. package/lib/__templates__/nextjs/template.config.js +4 -2
  24. package/lib/__templates__/nuxt-vue/.coze +5 -0
  25. package/lib/__templates__/nuxt-vue/package.json +1 -1
  26. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +5 -5
  27. package/lib/__templates__/nuxt-vue/scripts/build.ps1 +12 -0
  28. package/lib/__templates__/nuxt-vue/scripts/dev.ps1 +63 -0
  29. package/lib/__templates__/nuxt-vue/scripts/dev.sh +232 -3
  30. package/lib/__templates__/nuxt-vue/scripts/prepare.ps1 +12 -0
  31. package/lib/__templates__/nuxt-vue/scripts/start.ps1 +11 -0
  32. package/lib/__templates__/nuxt-vue/scripts/validate.ps1 +8 -0
  33. package/lib/__templates__/phaser/.coze +18 -0
  34. package/lib/__templates__/phaser/README.md +45 -0
  35. package/lib/__templates__/phaser/_gitignore +23 -0
  36. package/lib/__templates__/phaser/_npmrc +14 -0
  37. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  38. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  39. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  40. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  41. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  42. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  43. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  44. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  45. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  46. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  47. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  48. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  49. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  50. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  51. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  52. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  53. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  54. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  55. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  56. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  57. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  58. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  59. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  60. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  61. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  62. package/lib/__templates__/phaser/debug-runtime/flow-graph.json +20 -0
  63. package/lib/__templates__/phaser/index.html +12 -0
  64. package/lib/__templates__/phaser/package.json +31 -0
  65. package/lib/__templates__/phaser/pnpm-lock.yaml +661 -0
  66. package/lib/__templates__/phaser/scripts/build.ps1 +12 -0
  67. package/lib/__templates__/phaser/scripts/build.sh +8 -0
  68. package/lib/__templates__/phaser/scripts/dev.ps1 +49 -0
  69. package/lib/__templates__/phaser/scripts/dev.sh +82 -0
  70. package/lib/__templates__/phaser/scripts/prepare.ps1 +8 -0
  71. package/lib/__templates__/phaser/scripts/prepare.sh +7 -0
  72. package/lib/__templates__/phaser/scripts/spawn-detached.cjs +29 -0
  73. package/lib/__templates__/phaser/scripts/start.ps1 +49 -0
  74. package/lib/__templates__/phaser/scripts/start.sh +82 -0
  75. package/lib/__templates__/phaser/scripts/validate.ps1 +7 -0
  76. package/lib/__templates__/phaser/scripts/validate.sh +7 -0
  77. package/lib/__templates__/phaser/src/assets.ts +15 -0
  78. package/lib/__templates__/phaser/src/global.d.ts +39 -0
  79. package/lib/__templates__/phaser/src/main.ts +28 -0
  80. package/lib/__templates__/phaser/src/scene/BootScene.ts +213 -0
  81. package/lib/__templates__/phaser/src/scene/DevLoadingScene.ts +69 -0
  82. package/lib/__templates__/phaser/src/style.css +28 -0
  83. package/lib/__templates__/phaser/src/types.ts +32 -0
  84. package/lib/__templates__/phaser/src/utils/asset-loader.ts +319 -0
  85. package/lib/__templates__/phaser/src/utils/index.ts +2 -0
  86. package/lib/__templates__/phaser/src/utils/mark-editable.ts +13 -0
  87. package/lib/__templates__/phaser/template.config.js +43 -0
  88. package/lib/__templates__/phaser/tsconfig.json +23 -0
  89. package/lib/__templates__/phaser/vite.config.ts +96 -0
  90. package/lib/__templates__/phaser-static/.coze +15 -0
  91. package/lib/__templates__/phaser-static/README.md +40 -0
  92. package/lib/__templates__/phaser-static/_gitignore +6 -0
  93. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  94. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  95. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  96. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  97. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  98. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  99. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  100. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  101. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  102. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  103. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  104. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  105. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  106. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  107. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  108. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  109. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  110. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  111. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  112. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  113. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  114. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  115. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  116. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  117. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  118. package/lib/__templates__/phaser-static/debug-runtime/flow-graph.json +20 -0
  119. package/lib/__templates__/phaser-static/index.html +17 -0
  120. package/lib/__templates__/phaser-static/scripts/serve.ps1 +7 -0
  121. package/lib/__templates__/phaser-static/src/assets.js +15 -0
  122. package/lib/__templates__/phaser-static/src/main.js +26 -0
  123. package/lib/__templates__/phaser-static/src/phaser.js +7 -0
  124. package/lib/__templates__/phaser-static/src/scene/BootScene.js +205 -0
  125. package/lib/__templates__/phaser-static/src/scene/DevLoadingScene.js +67 -0
  126. package/lib/__templates__/phaser-static/src/style.css +28 -0
  127. package/lib/__templates__/phaser-static/src/utils/asset-loader.js +267 -0
  128. package/lib/__templates__/phaser-static/src/utils/index.js +2 -0
  129. package/lib/__templates__/phaser-static/src/utils/mark-editable.js +3 -0
  130. package/lib/__templates__/phaser-static/template.config.js +35 -0
  131. package/lib/__templates__/pi-agent/package.json +1 -1
  132. package/lib/__templates__/pi-agent/pnpm-lock.yaml +5 -5
  133. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +1 -1
  134. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +178 -68
  135. package/lib/__templates__/taro/AGENTS.md +11 -7
  136. package/lib/__templates__/taro/README.md +4 -4
  137. package/lib/__templates__/taro/config/index.ts +10 -2
  138. package/lib/__templates__/taro/pnpm-lock.yaml +5 -5
  139. package/lib/__templates__/taro/server/package.json +1 -1
  140. package/lib/__templates__/taro/server/src/main.ts +8 -2
  141. package/lib/__templates__/templates.json +51 -1
  142. package/lib/__templates__/vite/.coze +5 -0
  143. package/lib/__templates__/vite/package.json +1 -1
  144. package/lib/__templates__/vite/pnpm-lock.yaml +5 -5
  145. package/lib/__templates__/vite/scripts/build.ps1 +16 -0
  146. package/lib/__templates__/vite/scripts/dev.ps1 +63 -0
  147. package/lib/__templates__/vite/scripts/dev.sh +110 -1
  148. package/lib/__templates__/vite/scripts/prepare.ps1 +18 -0
  149. package/lib/__templates__/vite/scripts/start.ps1 +11 -0
  150. package/lib/__templates__/vite/scripts/validate.ps1 +8 -0
  151. package/lib/__templates__/vite/template.config.js +4 -2
  152. package/lib/cli.js +3718 -211
  153. package/lib/deploy/package-project.js +244 -0
  154. package/lib/deploy/package-project.py +183 -0
  155. package/package.json +5 -2
@@ -0,0 +1,267 @@
1
+ import { AUDIOS, IMAGES, IMAGE_SEQUENCES, VIDEOS } from '../assets.js';
2
+
3
+ const FRAME_KEY_MARKER = ':frame:';
4
+ const MANIFEST_KEY_SUFFIX = ':manifest';
5
+
6
+ const getFrameKey = (assetKey, frameIndex) =>
7
+ assetKey +
8
+ FRAME_KEY_MARKER +
9
+ String(frameIndex + 1).padStart(4, '0');
10
+
11
+ const getManifestKey = assetKey => assetKey + MANIFEST_KEY_SUFFIX;
12
+
13
+ const getFrameSource = (directory, frameIndex) =>
14
+ directory + '/frame_' + String(frameIndex + 1).padStart(4, '0') + '.png';
15
+
16
+ export const resolveAssetUrl = source => new URL(source, document.baseURI).href;
17
+
18
+ const isImageSequenceManifest = value => {
19
+ if (!value || typeof value !== 'object') {
20
+ return false;
21
+ }
22
+
23
+ return (
24
+ value.type === 'image_sequence' &&
25
+ typeof value.name === 'string' &&
26
+ typeof value.properties?.fps === 'number' &&
27
+ typeof value.properties.frame_count === 'number' &&
28
+ value.properties.frame_count > 0
29
+ );
30
+ };
31
+
32
+ const getImageSequenceManifest = (scene, assetKey) => {
33
+ const manifest = scene.cache.json.get(getManifestKey(assetKey));
34
+ if (!isImageSequenceManifest(manifest)) {
35
+ throw new Error('Invalid image sequence manifest: ' + assetKey);
36
+ }
37
+
38
+ return manifest;
39
+ };
40
+
41
+ const queueImageSequenceFrames = (scene, assetKey, directory, manifest) => {
42
+ Array.from(
43
+ { length: manifest.properties.frame_count },
44
+ (_value, frameIndex) => {
45
+ scene.load.image(
46
+ getFrameKey(assetKey, frameIndex),
47
+ resolveAssetUrl(getFrameSource(directory, frameIndex)),
48
+ );
49
+ },
50
+ );
51
+ };
52
+
53
+ const queueImages = (scene, images) => {
54
+ Object.entries(images).forEach(([assetKey, source]) => {
55
+ scene.load.image(assetKey, resolveAssetUrl(source));
56
+ });
57
+ return Object.keys(images).length;
58
+ };
59
+
60
+ const queueVideos = (scene, videos) => {
61
+ Object.entries(videos).forEach(([assetKey, source]) => {
62
+ scene.load.video(assetKey, resolveAssetUrl(source));
63
+ });
64
+ return Object.keys(videos).length;
65
+ };
66
+
67
+ const queueAudios = (scene, audios) => {
68
+ Object.entries(audios).forEach(([assetKey, source]) => {
69
+ scene.load.audio(assetKey, resolveAssetUrl(source));
70
+ });
71
+ return Object.keys(audios).length;
72
+ };
73
+
74
+ const queueImageSequences = (scene, imageSequences) =>
75
+ Object.entries(imageSequences).reduce(
76
+ (fileCount, [assetKey, directory]) => {
77
+ const manifestKey = getManifestKey(assetKey);
78
+ const cachedManifest = scene.cache.json.get(manifestKey);
79
+
80
+ if (isImageSequenceManifest(cachedManifest)) {
81
+ queueImageSequenceFrames(scene, assetKey, directory, cachedManifest);
82
+ return fileCount + cachedManifest.properties.frame_count;
83
+ }
84
+
85
+ scene.load.once(
86
+ 'filecomplete-json-' + manifestKey,
87
+ (_key, _type, data) => {
88
+ if (!isImageSequenceManifest(data)) {
89
+ throw new Error('Invalid image sequence manifest: ' + assetKey);
90
+ }
91
+
92
+ queueImageSequenceFrames(scene, assetKey, directory, data);
93
+ },
94
+ );
95
+
96
+ scene.load.json(
97
+ manifestKey,
98
+ resolveAssetUrl(directory + '/manifest.json'),
99
+ );
100
+ return fileCount + 1;
101
+ },
102
+ 0,
103
+ );
104
+
105
+ export const loadAssets = (scene, collections) =>
106
+ queueImages(scene, collections.images ?? {}) +
107
+ queueImageSequences(scene, collections.imageSequences ?? {}) +
108
+ queueVideos(scene, collections.videos ?? {}) +
109
+ queueAudios(scene, collections.audios ?? {});
110
+
111
+ export const getImageSequenceRuntimeKeys = assetKey => ({
112
+ texture: getFrameKey(assetKey, 0),
113
+ animation: assetKey + ':animation',
114
+ });
115
+
116
+ export const getAssetKeyFromFileKey = fileKey =>
117
+ fileKey.split(FRAME_KEY_MARKER)[0]?.replace(MANIFEST_KEY_SUFFIX, '') ??
118
+ fileKey;
119
+
120
+ export const getAssetLabelFromFileKey = (fileKey, collections) => {
121
+ const assetKey = getAssetKeyFromFileKey(fileKey);
122
+
123
+ if (assetKey in (collections.images ?? {})) {
124
+ return '图片 ' + assetKey;
125
+ }
126
+ if (assetKey in (collections.imageSequences ?? {})) {
127
+ return '帧动画 ' + assetKey;
128
+ }
129
+ if (assetKey in (collections.videos ?? {})) {
130
+ return '视频 ' + assetKey;
131
+ }
132
+ if (assetKey in (collections.audios ?? {})) {
133
+ return '音频 ' + assetKey;
134
+ }
135
+
136
+ return assetKey;
137
+ };
138
+
139
+ export const registerImageSequenceAnimations = (scene, imageSequences) => {
140
+ Object.keys(imageSequences).forEach(assetKey => {
141
+ const manifest = getImageSequenceManifest(scene, assetKey);
142
+ const animationKey = getImageSequenceRuntimeKeys(assetKey).animation;
143
+ if (scene.anims.exists(animationKey)) {
144
+ return;
145
+ }
146
+
147
+ scene.anims.create({
148
+ key: animationKey,
149
+ frames: Array.from(
150
+ { length: manifest.properties.frame_count },
151
+ (_value, frameIndex) => ({ key: getFrameKey(assetKey, frameIndex) }),
152
+ ),
153
+ frameRate: manifest.properties.fps,
154
+ repeat: -1,
155
+ });
156
+ });
157
+ };
158
+
159
+ const findAsset = assetKey => {
160
+ const matches = [];
161
+
162
+ if (Object.hasOwn(IMAGES, assetKey)) {
163
+ matches.push({
164
+ collections: { images: { [assetKey]: IMAGES[assetKey] } },
165
+ loadedAsset: { kind: 'image', key: assetKey },
166
+ });
167
+ }
168
+ if (Object.hasOwn(IMAGE_SEQUENCES, assetKey)) {
169
+ matches.push({
170
+ collections: {
171
+ imageSequences: { [assetKey]: IMAGE_SEQUENCES[assetKey] },
172
+ },
173
+ loadedAsset: {
174
+ kind: 'image-sequence',
175
+ key: assetKey,
176
+ ...getImageSequenceRuntimeKeys(assetKey),
177
+ },
178
+ });
179
+ }
180
+ if (Object.hasOwn(VIDEOS, assetKey)) {
181
+ matches.push({
182
+ collections: { videos: { [assetKey]: VIDEOS[assetKey] } },
183
+ loadedAsset: { kind: 'video', key: assetKey },
184
+ });
185
+ }
186
+ if (Object.hasOwn(AUDIOS, assetKey)) {
187
+ matches.push({
188
+ collections: { audios: { [assetKey]: AUDIOS[assetKey] } },
189
+ loadedAsset: { kind: 'audio', key: assetKey },
190
+ });
191
+ }
192
+
193
+ if (matches.length === 0) {
194
+ throw new Error('Asset not found: ' + assetKey);
195
+ }
196
+ if (matches.length > 1) {
197
+ throw new Error('Duplicate asset key: ' + assetKey);
198
+ }
199
+
200
+ return matches[0];
201
+ };
202
+
203
+ const isAssetLoaded = (scene, loadedAsset) => {
204
+ switch (loadedAsset.kind) {
205
+ case 'image':
206
+ return scene.textures.exists(loadedAsset.key);
207
+ case 'image-sequence':
208
+ return scene.anims.exists(loadedAsset.animation);
209
+ case 'video':
210
+ return scene.cache.video.exists(loadedAsset.key);
211
+ case 'audio':
212
+ return scene.cache.audio.exists(loadedAsset.key);
213
+ default:
214
+ return false;
215
+ }
216
+ };
217
+
218
+ export const loadAsset = async (scene, assetKey) => {
219
+ const { collections, loadedAsset } = findAsset(assetKey);
220
+ if (isAssetLoaded(scene, loadedAsset)) {
221
+ return loadedAsset;
222
+ }
223
+
224
+ return new Promise((resolve, reject) => {
225
+ const cleanup = () => {
226
+ scene.load.off('complete', onComplete);
227
+ scene.load.off('loaderror', onLoadError);
228
+ };
229
+
230
+ const onComplete = () => {
231
+ cleanup();
232
+ try {
233
+ if (loadedAsset.kind === 'image-sequence') {
234
+ registerImageSequenceAnimations(
235
+ scene,
236
+ collections.imageSequences ?? {},
237
+ );
238
+ }
239
+ resolve(loadedAsset);
240
+ } catch (error) {
241
+ reject(error);
242
+ }
243
+ };
244
+
245
+ const onLoadError = file => {
246
+ if (getAssetKeyFromFileKey(file.key) !== assetKey) {
247
+ return;
248
+ }
249
+
250
+ cleanup();
251
+ reject(new Error('Failed to load asset: ' + assetKey));
252
+ };
253
+
254
+ scene.load.once('complete', onComplete);
255
+ scene.load.on('loaderror', onLoadError);
256
+
257
+ try {
258
+ loadAssets(scene, collections);
259
+ if (!scene.load.isLoading()) {
260
+ scene.load.start();
261
+ }
262
+ } catch (error) {
263
+ cleanup();
264
+ reject(error);
265
+ }
266
+ });
267
+ };
@@ -0,0 +1,2 @@
1
+ export * from './asset-loader.js';
2
+ export * from './mark-editable.js';
@@ -0,0 +1,3 @@
1
+ export const markEditable = (locator, gameObject, options) =>
2
+ window.PhaserBridge?.markEditable?.(locator, gameObject, options) ??
3
+ gameObject;
@@ -0,0 +1,35 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+ export const paramsSchema = {
10
+ type: 'object',
11
+ properties: {
12
+ appName: {
13
+ type: 'string',
14
+ minLength: 1,
15
+ pattern: '^[a-z0-9-]+$',
16
+ description:
17
+ 'Application name (lowercase, alphanumeric and hyphens only)',
18
+ },
19
+ },
20
+ required: [],
21
+ additionalProperties: false,
22
+ };
23
+
24
+ const config = {
25
+ description:
26
+ 'Phaser Static(纯静态 2D 游戏):coze-dev init ${COZE_WORKSPACE_PATH} --template phaser-static\n' +
27
+ '- 适用:浏览器原生 JavaScript ESM 的 Phaser 3 游戏。\n' +
28
+ '- Phaser 与 Phaser Bridge 从 CDN 加载;无需 npm、Vite 或 TypeScript。',
29
+ paramsSchema,
30
+ defaultParams: {
31
+ appName: 'phaser-static-game',
32
+ },
33
+ };
34
+
35
+ export default config;
@@ -22,7 +22,7 @@
22
22
  "@mariozechner/pi-ai": "^0.63.2",
23
23
  "@mariozechner/pi-coding-agent": "^0.63.2",
24
24
  "@sinclair/typebox": "^0.34.48",
25
- "coze-coding-dev-sdk": "^0.7.17",
25
+ "coze-coding-dev-sdk": "^0.7.25",
26
26
  "@radix-ui/react-select": "^2.2.6",
27
27
  "@radix-ui/react-slot": "^1.2.4",
28
28
  "@streamdown/code": "^1.1.1",
@@ -36,8 +36,8 @@ importers:
36
36
  specifier: ^2.1.1
37
37
  version: 2.1.1
38
38
  coze-coding-dev-sdk:
39
- specifier: ^0.7.17
40
- version: 0.7.20(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
39
+ specifier: ^0.7.25
40
+ version: 0.7.25(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
41
41
  express:
42
42
  specifier: ^4.21.2
43
43
  version: 4.22.1
@@ -2085,8 +2085,8 @@ packages:
2085
2085
  cose-base@2.2.0:
2086
2086
  resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
2087
2087
 
2088
- coze-coding-dev-sdk@0.7.20:
2089
- resolution: {integrity: sha512-f3UeZcYB/udyWrlFrsOViM47PCW+Nk0I4FcXEGzcjEfaQsa1CPBpf/y5UbBM6GIZ1v+lnuh/MiBlAaALFeMTqw==}
2088
+ coze-coding-dev-sdk@0.7.25:
2089
+ resolution: {integrity: sha512-5683ngikMdsRW0slGTE2OAZ1Fyj0lhkC4fGNOwsqKc35e68/gtg9KmmtK9CK9SZ+5/oIn/4wLoHXFB0gHJtO2A==}
2090
2090
  engines: {node: '>=18.0.0'}
2091
2091
  hasBin: true
2092
2092
 
@@ -6120,7 +6120,7 @@ snapshots:
6120
6120
  dependencies:
6121
6121
  layout-base: 2.0.1
6122
6122
 
6123
- coze-coding-dev-sdk@0.7.20(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0):
6123
+ coze-coding-dev-sdk@0.7.25(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0):
6124
6124
  dependencies:
6125
6125
  '@langchain/core': 1.1.40(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
6126
6126
  '@langchain/openai': 1.4.3(@langchain/core@1.1.40(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))(ws@8.20.0)
@@ -4,7 +4,7 @@ set -Eeuo pipefail
4
4
  start_service() {
5
5
  cd "${COZE_WORKSPACE_PATH}/server/dist"
6
6
 
7
- local port="${DEPLOY_RUN_PORT:-3000}"
7
+ local port="${DEPLOY_RUN_PORT:-<%= serverPort %>}"
8
8
  echo "Starting Static File Server on port ${port} for deploy..."
9
9
 
10
10
  node ./main.js -p "${port}"
@@ -2,12 +2,17 @@
2
2
  echo "⚙️ dev_run.sh 开始运行"
3
3
  set -Eeuo pipefail
4
4
 
5
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
6
+ COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
5
7
  cd "${COZE_WORKSPACE_PATH}"
6
8
 
7
9
  # ---------------------------------------------------------
8
- # PID 文件,用于追踪上一次启动的进程树
10
+ # 项目级日志目录
9
11
  # ---------------------------------------------------------
10
- PID_FILE="/tmp/coze-dev-run.pid"
12
+ LOG_DIR="${COZE_WORKSPACE_PATH}/logs"
13
+ LOG_FILE="${LOG_DIR}/dev.log"
14
+ PID_FILE="${LOG_DIR}/dev.pid"
15
+ DEV_PID=""
11
16
 
12
17
  # ---------------------------------------------------------
13
18
  # 工具函数
@@ -25,99 +30,192 @@ kill_process_tree() {
25
30
  fi
26
31
  }
27
32
 
33
+ # detached 出去的进程没人负责回收,超过这个时长就自己退出,避免端口与内存长期泄露。
34
+ MAX_RUNTIME_SECONDS=1200
35
+
36
+ # 真正被 detach 的是这层 bash wrapper:它是进程组 leader,组内 watchdog 到点回收整组
37
+ # (wrapper -> pnpm -> taro/nest);被包的进程自己先退出时也顺手清空进程组,不留残余。
38
+ RUN_WITH_TIMEOUT='
39
+ timeout_seconds=$1
40
+ shift
41
+
42
+ "$@" &
43
+ child_pid=$!
44
+
45
+ # 先忽略 TERM,才能在向整组发 TERM(自己也在组里)之后存活下来补一发 KILL。
46
+ ( trap "" TERM
47
+ sleep "${timeout_seconds}"
48
+ echo "[dev] 后台进程运行超过 ${timeout_seconds}s,回收进程组 $$。"
49
+ kill -TERM -- "-$$" 2>/dev/null || true
50
+ sleep 5
51
+ kill -KILL -- "-$$" 2>/dev/null || true
52
+ ) &
53
+
54
+ wait "${child_pid}"
55
+ kill -KILL -- "-$$" 2>/dev/null || true
56
+ '
57
+
58
+ # coze-daemon 会在 runtime shell 退出时清理原进程组。
59
+ # 通过 Node detached spawn 创建独立 session/进程组,并将 stdio 直接写入日志。
60
+ # 返回的 PID 是 wrapper 的,同时也是整个进程组的 PGID,后续按组回收。
61
+ spawn_detached() {
62
+ local cwd="$1"
63
+ local log_file="$2"
64
+ shift 2
65
+
66
+ node - "$cwd" "$log_file" \
67
+ /bin/bash -c "${RUN_WITH_TIMEOUT}" detached-runner "${MAX_RUNTIME_SECONDS}" "$@" <<'NODE'
68
+ const fs = require('node:fs');
69
+ const { spawn } = require('node:child_process');
70
+
71
+ const [cwd, logFile, command, ...args] = process.argv.slice(2);
72
+ if (!cwd || !logFile || !command) {
73
+ throw new Error('spawn_detached 缺少 cwd、log_file 或 command');
74
+ }
75
+
76
+ const logFd = fs.openSync(logFile, 'a');
77
+ try {
78
+ const child = spawn(command, args, {
79
+ cwd,
80
+ detached: true,
81
+ env: process.env,
82
+ stdio: ['ignore', logFd, logFd],
83
+ });
84
+ child.unref();
85
+ process.stdout.write(String(child.pid));
86
+ } finally {
87
+ fs.closeSync(logFd);
88
+ }
89
+ NODE
90
+ }
91
+
92
+ stop_detached() {
93
+ local pid="${1:-}"
94
+ if [[ -z "${pid}" ]]; then
95
+ return
96
+ fi
97
+
98
+ kill -TERM -- "-${pid}" 2>/dev/null || kill -TERM "${pid}" 2>/dev/null || true
99
+ sleep 1
100
+ kill -KILL -- "-${pid}" 2>/dev/null || true
101
+ }
102
+
103
+ process_belongs_to_workspace() {
104
+ local pid=$1
105
+ local cwd=""
106
+
107
+ if [[ -L "/proc/${pid}/cwd" ]]; then
108
+ cwd=$(readlink "/proc/${pid}/cwd" 2>/dev/null || true)
109
+ elif command -v lsof >/dev/null 2>&1; then
110
+ cwd=$(lsof -a -p "${pid}" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -1)
111
+ fi
112
+
113
+ case "${cwd}" in
114
+ "${COZE_WORKSPACE_PATH}"|"${COZE_WORKSPACE_PATH}"/*) return 0 ;;
115
+ *) return 1 ;;
116
+ esac
117
+ }
118
+
119
+ process_group_belongs_to_workspace() {
120
+ local pgid=$1
121
+ local pids
122
+ local found=0
123
+
124
+ pids=$(ps -axo pid=,pgid= 2>/dev/null | awk -v pgid="${pgid}" '$2 == pgid {print $1}')
125
+ for pid in ${pids}; do
126
+ found=1
127
+ if ! process_belongs_to_workspace "${pid}"; then
128
+ return 1
129
+ fi
130
+ done
131
+
132
+ [[ "${found}" -eq 1 ]]
133
+ }
134
+
135
+ # 优先用 ss,无结果或缺少 ss 时回落到 lsof;两者都不可用则显式告警,避免静默误判为端口空闲
136
+ list_port_pids() {
137
+ local port=$1
138
+ local pids=""
139
+ local has_tool=0
140
+
141
+ if command -v ss >/dev/null 2>&1; then
142
+ has_tool=1
143
+ pids=$(ss -H -lntp 2>/dev/null | awk -v port="${port}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u | paste -sd' ' - || true)
144
+ fi
145
+ if [[ -z "${pids}" ]] && command -v lsof >/dev/null 2>&1; then
146
+ has_tool=1
147
+ pids=$(lsof -t -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null | sort -u | paste -sd' ' - || true)
148
+ fi
149
+ if [[ "${has_tool}" -eq 0 ]]; then
150
+ echo "Warning: neither ss nor lsof available, cannot inspect port ${port}." >&2
151
+ fi
152
+
153
+ echo "${pids}"
154
+ }
155
+
28
156
  kill_port_if_listening() {
29
157
  local port=$1
30
158
  local pids
31
- pids=$(ss -H -lntp 2>/dev/null | awk -v port="${port}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u | paste -sd' ' - || true)
159
+ pids=$(list_port_pids "${port}")
32
160
  if [[ -z "${pids}" ]]; then
33
161
  echo "Port ${port} is free."
34
162
  return
35
163
  fi
36
- echo "Port ${port} in use by PIDs: ${pids}"
164
+ echo "Port ${port} in use by PIDs: ${pids} (SIGKILL)"
37
165
  for pid in ${pids}; do
38
166
  kill_process_tree "${pid}"
39
167
  done
40
168
  sleep 1
41
- pids=$(ss -H -lntp 2>/dev/null | awk -v port="${port}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u | paste -sd' ' - || true)
169
+ pids=$(list_port_pids "${port}")
42
170
  if [[ -n "${pids}" ]]; then
43
- echo "Warning: port ${port} still busy after cleanup, PIDs: ${pids}"
171
+ echo "Warning: port ${port} still busy after SIGKILL, PIDs: ${pids}"
44
172
  else
45
173
  echo "Port ${port} cleared."
46
174
  fi
47
175
  }
48
176
 
49
- # ---------------------------------------------------------
50
- # 1. 清理上一次运行残留的整棵进程树
51
- # ---------------------------------------------------------
177
+ # 只清理当前项目上次记录的 detached 进程。
52
178
  cleanup_previous_run() {
53
- # 1a. 通过 PID 文件清理上次的进程树
54
- if [[ -f "${PID_FILE}" ]]; then
55
- local old_pid
56
- old_pid=$(cat "${PID_FILE}" 2>/dev/null || true)
57
- if [[ -n "${old_pid}" ]] && kill -0 "${old_pid}" 2>/dev/null; then
58
- echo "🧹 Killing previous dev process tree (root PID: ${old_pid})..."
59
- kill_process_tree "${old_pid}"
60
- fi
61
- rm -f "${PID_FILE}"
179
+ if [[ ! -f "${PID_FILE}" ]]; then
180
+ return
62
181
  fi
63
182
 
64
- # 1b. 兜底:按特征匹配清理所有残留的相关进程(排除自身)
65
- echo "🧹 Cleaning up any orphaned dev processes..."
66
- local patterns=(
67
- "pnpm dev"
68
- "concurrently.*dev:web.*dev:server"
69
- "nest start --watch"
70
- "taro build --type h5 --watch"
71
- "node --enable-source-maps.*/workspace/projects/server/dist/main"
72
- "esbuild --service.*--ping"
73
- )
74
- for pattern in "${patterns[@]}"; do
75
- local pids
76
- pids=$(pgrep -f "${pattern}" 2>/dev/null || true)
77
- for pid in ${pids}; do
78
- # 不杀自己和自己的父进程链
79
- if [[ "${pid}" != "$$" ]] && [[ "${pid}" != "${PPID}" ]]; then
80
- echo " Killing orphan PID ${pid} matching '${pattern}'"
81
- kill -9 "${pid}" 2>/dev/null || true
82
- fi
83
- done
84
- done
85
- sleep 1
183
+ local old_pid
184
+ old_pid=$(cat "${PID_FILE}" 2>/dev/null || true)
185
+ if [[ "${old_pid}" =~ ^[1-9][0-9]*$ ]] && process_group_belongs_to_workspace "${old_pid}"; then
186
+ echo "🧹 Killing previous dev process group (PGID: ${old_pid})..."
187
+ stop_detached "${old_pid}"
188
+ fi
189
+ rm -f "${PID_FILE}"
86
190
  }
87
191
 
88
192
  # ---------------------------------------------------------
89
193
  # 2. 安装依赖
90
194
  # ---------------------------------------------------------
91
195
  echo "📦 Installing dependencies..."
92
- pnpm install
196
+ PNPM_BIN="$(command -v pnpm)"
197
+ "${PNPM_BIN}" install
93
198
  echo "✅ Dependencies installed successfully!"
94
199
 
95
200
  # ---------------------------------------------------------
96
201
  # 3. 清理旧进程 + 端口
97
202
  # ---------------------------------------------------------
98
- SERVER_PORT=3000
203
+ # DEPLOY_RUN_PORT 是平台注入的对外端口,项目内统一使用 PORT。
204
+ PORT="${DEPLOY_RUN_PORT:-${PORT:-<%= port %>}}"
205
+ SERVER_PORT="${SERVER_PORT:-<%= serverPort %>}"
206
+ export PORT
207
+ export SERVER_PORT
99
208
 
209
+ mkdir -p "${LOG_DIR}"
100
210
  cleanup_previous_run
101
211
 
102
- echo "Clearing port ${DEPLOY_RUN_PORT} (web) before start."
103
- kill_port_if_listening "${DEPLOY_RUN_PORT}"
212
+ echo "Clearing port ${PORT} (web) before start."
213
+ kill_port_if_listening "${PORT}"
104
214
  echo "Clearing port ${SERVER_PORT} (server) before start."
105
215
  kill_port_if_listening "${SERVER_PORT}"
106
216
 
107
217
  # ---------------------------------------------------------
108
- # 4. 退出时自动清理子进程(信号 trap)
109
- # ---------------------------------------------------------
110
- cleanup_on_exit() {
111
- echo "🛑 dev_run.sh exiting, cleaning up child processes..."
112
- # 杀掉当前脚本的所有子进程
113
- kill -- -$$ 2>/dev/null || true
114
- rm -f "${PID_FILE}"
115
- exit 0
116
- }
117
- trap cleanup_on_exit EXIT INT TERM HUP
118
-
119
- # ---------------------------------------------------------
120
- # 5. 启动服务
218
+ # 4. 启动服务
121
219
  # ---------------------------------------------------------
122
220
  start_service() {
123
221
  cd "${COZE_WORKSPACE_PATH}"
@@ -133,19 +231,31 @@ start_service() {
133
231
  # 启动 Taro H5 和 NestJS Server
134
232
  echo "Starting Taro H5 Dev Server and NestJS Server..."
135
233
 
136
- export PORT=${DEPLOY_RUN_PORT}
137
- rm -f /tmp/coze-logs/dev.log
138
- mkdir -p /tmp/coze-logs
234
+ : > "${LOG_FILE}"
235
+
236
+ DEV_PID="$(spawn_detached \
237
+ "${COZE_WORKSPACE_PATH}" \
238
+ "${LOG_FILE}" \
239
+ "${PNPM_BIN}" dev)"
240
+ if [[ -z "${DEV_PID}" ]]; then
241
+ echo "❌ 无法获取 dev 后台进程 PID"
242
+ return 1
243
+ fi
244
+ echo "${DEV_PID}" > "${PID_FILE}"
139
245
 
140
- # 后台启动并记录 PID
141
- pnpm dev 2>&1 | tee /tmp/coze-logs/dev.log &
142
- local dev_pid=$!
143
- echo "${dev_pid}" > "${PID_FILE}"
144
- echo "📝 Dev process started with PID: ${dev_pid} (saved to ${PID_FILE})"
246
+ sleep 1
247
+ if ! kill -0 "${DEV_PID}" 2>/dev/null; then
248
+ echo "❌ Dev service failed to start. See ${LOG_FILE}." >&2
249
+ tail -n 20 "${LOG_FILE}" >&2 || true
250
+ rm -f "${PID_FILE}"
251
+ return 1
252
+ fi
145
253
 
146
- # 前台等待,保证 trap 能正常捕获信号
147
- wait "${dev_pid}" || true
254
+ echo "📝 Dev process started with PID: ${DEV_PID}"
255
+ echo "Auto stop after ${MAX_RUNTIME_SECONDS}s."
256
+ echo "Log file: ${LOG_FILE}"
257
+ echo "PID file: ${PID_FILE}"
148
258
  }
149
259
 
150
- echo "Starting HTTP services on port ${DEPLOY_RUN_PORT} (web) and ${SERVER_PORT} (server)..."
260
+ echo "Starting HTTP services on port ${PORT} (web) and ${SERVER_PORT} (server)..."
151
261
  start_service