@playdrop/playdrop-cli 0.7.3 → 0.7.4

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 CHANGED
@@ -16,11 +16,11 @@ npm install -g @playdrop/playdrop-cli
16
16
 
17
17
  ```bash
18
18
  playdrop auth login
19
- playdrop auth whoami
19
+ playdrop auth whoami --env dev
20
20
  playdrop project init .
21
21
  playdrop project create app my-first-game --template playdrop/template/typescript_template
22
22
  playdrop project dev my-first-game
23
- playdrop project publish .
23
+ playdrop project publish --env dev .
24
24
  ```
25
25
 
26
26
  ## What You Can Do
@@ -52,6 +52,7 @@ playdrop project publish .
52
52
 
53
53
  ```bash
54
54
  playdrop auth login
55
+ playdrop auth whoami --env dev
55
56
  playdrop browse
56
57
  playdrop search "city builder"
57
58
  playdrop detail playdrop/app/hangingout
@@ -65,7 +66,7 @@ playdrop ai create image "Wide fantasy vista" --ratio 16:9 --size 4K
65
66
  playdrop project init .
66
67
  playdrop project create app my-app --template playdrop/template/html_template
67
68
  playdrop project dev my-app
68
- playdrop project publish .
69
+ playdrop project publish --env dev .
69
70
  playdrop documentation browse
70
71
  playdrop feedback send --title "Bug report" --comment "Details here."
71
72
  playdrop getting-started
@@ -95,10 +96,11 @@ Examples:
95
96
 
96
97
  ```bash
97
98
  playdrop auth login
99
+ playdrop auth whoami --env dev
98
100
  playdrop project init .
99
101
  playdrop project create app my-app --template playdrop/template/html_template
100
102
  playdrop project dev my-app
101
- playdrop project publish .
103
+ playdrop project publish --env dev .
102
104
  ```
103
105
 
104
106
  ## Documentation
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.7.3",
3
- "build": 1,
2
+ "version": "0.7.4",
3
+ "build": 3,
4
4
  "platforms": {
5
5
  "ios": {
6
6
  "minimumVersion": "16.0"
@@ -27,11 +27,11 @@
27
27
  "clients": {
28
28
  "web": {
29
29
  "minimumVersion": "0.7.3",
30
- "minimumBuild": 1
30
+ "minimumBuild": 2
31
31
  },
32
32
  "admin": {
33
33
  "minimumVersion": "0.7.3",
34
- "minimumBuild": 1
34
+ "minimumBuild": 2
35
35
  },
36
36
  "apple": {
37
37
  "minimumVersion": "0.3.10",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "android-games": {
49
49
  "minimumVersion": "0.7.3",
50
- "minimumBuild": 1
50
+ "minimumBuild": 2
51
51
  },
52
52
  "cli": {
53
53
  "minimumVersion": "0.7.3"
@@ -58,6 +58,17 @@ function formatHostedLaunchCheckFailure(taskName, result, scope = 'local') {
58
58
  function normalizeLaunchCheckFailure(error, artifactFingerprint) {
59
59
  const rawMessage = error instanceof Error ? error.message : String(error);
60
60
  const message = normalizeLaunchCheckMessage(rawMessage);
61
+ const errorCodeMatch = message.match(/\berrorCode=([a-z0-9_]+)\b/i);
62
+ if (errorCodeMatch?.[1]) {
63
+ return buildFailedLaunchCheckResult(errorCodeMatch[1], message, artifactFingerprint);
64
+ }
65
+ const prefixedCodeMatch = message.match(/^([a-z0-9_]+):/i);
66
+ if (prefixedCodeMatch?.[1]
67
+ && (prefixedCodeMatch[1].startsWith('hosted_app_')
68
+ || prefixedCodeMatch[1].startsWith('local_dev_')
69
+ || prefixedCodeMatch[1].startsWith('dev_auth_'))) {
70
+ return buildFailedLaunchCheckResult(prefixedCodeMatch[1], message, artifactFingerprint);
71
+ }
61
72
  if (/errorCode=missing_init\b/i.test(message) || /hosted_app_missing_client_init:/i.test(message)) {
62
73
  return buildFailedLaunchCheckResult('missing_init', message, artifactFingerprint);
63
74
  }
@@ -111,12 +111,15 @@ async function prepareOwnedAssetUpload(task) {
111
111
  uploadKey: task.name,
112
112
  runtimeKey: task.runtimeKey,
113
113
  name: task.name,
114
+ displayName: task.displayName,
115
+ description: task.description,
114
116
  category: task.category,
115
117
  subcategory: task.subcategory,
116
118
  format: task.format || (0, node_path_1.extname)(files[0]?.filename || '').replace(/^\./, '').toUpperCase() || 'GLB',
117
119
  visibility: task.visibility,
118
120
  license: task.license,
119
121
  assetSpec: task.assetSpec,
122
+ tags: task.tags,
120
123
  shopListed: task.shopListed,
121
124
  shopPriceCredits: task.shopPriceCredits,
122
125
  files,
@@ -311,12 +314,15 @@ async function uploadAppVersion(client, task, artifacts, options) {
311
314
  uploadKey: ownedAsset.uploadKey,
312
315
  runtimeKey: ownedAsset.runtimeKey,
313
316
  name: ownedAsset.name,
317
+ displayName: ownedAsset.displayName,
318
+ description: ownedAsset.description,
314
319
  category: ownedAsset.category,
315
320
  subcategory: ownedAsset.subcategory,
316
321
  format: ownedAsset.format,
317
322
  assetSpec: ownedAsset.assetSpec,
318
323
  visibility: ownedAsset.visibility,
319
324
  license: ownedAsset.license,
325
+ tags: ownedAsset.tags,
320
326
  shopListed: ownedAsset.shopListed,
321
327
  shopPriceCredits: ownedAsset.shopPriceCredits,
322
328
  files: ownedAsset.files.map((file) => ({
@@ -79,6 +79,10 @@ function normalizeComparableUrl(rawUrl) {
79
79
  const pathname = parsed.pathname.replace(/\/+$/, '') || '/';
80
80
  return `${parsed.origin}${pathname}`;
81
81
  }
82
+ function resolveAutomationOrigin(targetUrl) {
83
+ const parsed = new URL(targetUrl);
84
+ return parsed.searchParams.get('launchCheck') === '1' ? parsed.origin : null;
85
+ }
82
86
  function normalizeHostedLaunchStatePayload(payload) {
83
87
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
84
88
  return null;
@@ -511,7 +515,10 @@ async function runCapture(options) {
511
515
  await page.screenshot({ path: options.screenshotPath, fullPage: true });
512
516
  console.log(`[capture] Saved screenshot to ${(0, node_path_1.relative)(process.cwd(), options.screenshotPath) || options.screenshotPath}`);
513
517
  }
514
- }, options.contextOptions ?? {});
518
+ }, {
519
+ ...(options.contextOptions ?? {}),
520
+ automationOrigin: resolveAutomationOrigin(options.targetUrl),
521
+ });
515
522
  }
516
523
  finally {
517
524
  await writeLogFile(options.logPath, outputLines);
@@ -104,12 +104,15 @@ export type AssetSpecCatalogueEntry = {
104
104
  export type OwnedAssetCatalogueEntry = {
105
105
  name: string;
106
106
  runtimeKey?: string;
107
+ displayName?: string;
108
+ description?: string;
107
109
  category?: string;
108
110
  subcategory?: string;
109
111
  format?: string;
110
112
  files?: Record<string, string>;
111
113
  visibility?: string;
112
114
  license?: string;
115
+ tags?: string[];
113
116
  shopListed?: boolean;
114
117
  shopPriceCredits?: number;
115
118
  };
@@ -254,6 +257,8 @@ export type OwnedAssetTask = {
254
257
  appName: string;
255
258
  name: string;
256
259
  runtimeKey?: string;
260
+ displayName?: string;
261
+ description?: string;
257
262
  cataloguePath: string;
258
263
  category: string;
259
264
  subcategory: string | null;
@@ -262,6 +267,7 @@ export type OwnedAssetTask = {
262
267
  format?: string;
263
268
  visibility?: string;
264
269
  license: ContentLicense;
270
+ tags: string[];
265
271
  shopListed?: boolean;
266
272
  shopPriceCredits?: number;
267
273
  files: Record<string, string>;
@@ -270,6 +276,8 @@ export type OwnedAssetTask = {
270
276
  export type PackOwnedAssetTask = {
271
277
  name: string;
272
278
  cataloguePath: string;
279
+ displayName?: string;
280
+ description?: string;
273
281
  category: string;
274
282
  subcategory: string | null;
275
283
  assetSpec?: string;
@@ -277,6 +285,7 @@ export type PackOwnedAssetTask = {
277
285
  format?: string;
278
286
  visibility?: string;
279
287
  license: ContentLicense;
288
+ tags: string[];
280
289
  shopListed?: boolean;
281
290
  shopPriceCredits?: number;
282
291
  files: Record<string, string>;
package/dist/catalogue.js CHANGED
@@ -1382,11 +1382,17 @@ function buildAppTasks(rootDir, catalogues, options) {
1382
1382
  if (!ownedLicense) {
1383
1383
  continue;
1384
1384
  }
1385
+ const ownedTags = normalizeCatalogueTags(owned.tags, errors, `[${label}] Owned asset "${ownedName}"`);
1386
+ if (ownedTags === null) {
1387
+ continue;
1388
+ }
1385
1389
  ownedAssets.push({
1386
1390
  kind: 'owned-asset',
1387
1391
  appName: rawName,
1388
1392
  name: ownedName,
1389
1393
  runtimeKey,
1394
+ displayName: typeof owned?.displayName === 'string' && owned.displayName.trim().length > 0 ? owned.displayName.trim() : undefined,
1395
+ description: typeof owned?.description === 'string' ? owned.description : undefined,
1390
1396
  cataloguePath: label,
1391
1397
  category,
1392
1398
  subcategory,
@@ -1394,6 +1400,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1394
1400
  format: ownedFormat,
1395
1401
  visibility: normalizeAssetVisibilityValue(owned?.visibility, errors, `${label} owned asset "${ownedName}"`),
1396
1402
  license: ownedLicense,
1403
+ tags: ownedTags,
1397
1404
  shopListed: typeof owned?.shopListed === 'boolean' ? owned.shopListed : undefined,
1398
1405
  shopPriceCredits: Number.isInteger(owned?.shopPriceCredits) ? Number(owned.shopPriceCredits) : undefined,
1399
1406
  files,
@@ -1922,15 +1929,22 @@ function buildAssetPackTasks(catalogues) {
1922
1929
  if (!ownedLicense) {
1923
1930
  continue;
1924
1931
  }
1932
+ const ownedTags = normalizeCatalogueTags(owned.tags, errors, `[${label}] Asset pack "${name}" owned asset "${ownedName}"`);
1933
+ if (ownedTags === null) {
1934
+ continue;
1935
+ }
1925
1936
  ownedAssets.push({
1926
1937
  name: ownedName,
1927
1938
  cataloguePath: label,
1939
+ displayName: typeof owned?.displayName === 'string' && owned.displayName.trim().length > 0 ? owned.displayName.trim() : undefined,
1940
+ description: typeof owned?.description === 'string' ? owned.description : undefined,
1928
1941
  category,
1929
1942
  subcategory,
1930
1943
  assetSpec,
1931
1944
  format: ownedFormat,
1932
1945
  visibility: normalizeAssetVisibilityValue(owned?.visibility, errors, `${label} asset pack "${name}" owned asset "${ownedName}"`),
1933
1946
  license: ownedLicense,
1947
+ tags: ownedTags,
1934
1948
  shopListed: typeof owned?.shopListed === 'boolean' ? owned.shopListed : undefined,
1935
1949
  shopPriceCredits: Number.isInteger(owned?.shopPriceCredits) ? Number(owned.shopPriceCredits) : undefined,
1936
1950
  files,
@@ -15,6 +15,7 @@ export type EnvironmentContext = {
15
15
  };
16
16
  type EnvironmentCallback = (ctx: EnvironmentContext) => Promise<void> | void;
17
17
  export type ResolveAuthenticatedEnvironmentOptions = {
18
+ env?: string;
18
19
  workspacePath?: string;
19
20
  };
20
21
  export declare function resolveAuthenticatedEnvironmentContext(command: string, actionLabel: string, options?: ResolveAuthenticatedEnvironmentOptions): Promise<EnvironmentContext | null>;
@@ -10,6 +10,13 @@ const environment_1 = require("./environment");
10
10
  const messages_1 = require("./messages");
11
11
  const workspaceAuth_1 = require("./workspaceAuth");
12
12
  const DEFAULT_PUBLIC_ENV = 'prod';
13
+ function normalizeRequestedEnv(value) {
14
+ if (typeof value !== 'string') {
15
+ return undefined;
16
+ }
17
+ const normalized = value.trim();
18
+ return normalized.length > 0 ? normalized : undefined;
19
+ }
13
20
  function resolveConfiguredEnvironment(envName, command, options) {
14
21
  if (!envName && options.requireAuth) {
15
22
  (0, messages_1.printConfigEnvironmentMissing)(command);
@@ -113,11 +120,12 @@ async function loadWorkspaceAwareConfig(command, options) {
113
120
  }
114
121
  return { cfg, workspaceAuth };
115
122
  }
116
- function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth) {
123
+ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, requestedEnv) {
117
124
  const matchingSessions = (0, config_1.listAccountSessionsForUsername)(workspaceAuth.config.ownerUsername, cfg);
118
- if (workspaceAuth.config.env) {
125
+ const preferredEnv = normalizeRequestedEnv(requestedEnv) ?? workspaceAuth.config.env;
126
+ if (preferredEnv) {
119
127
  return {
120
- account: (0, config_1.findAccountSession)(workspaceAuth.config.ownerUsername, workspaceAuth.config.env, cfg),
128
+ account: (0, config_1.findAccountSession)(workspaceAuth.config.ownerUsername, preferredEnv, cfg),
121
129
  matchingSessions,
122
130
  };
123
131
  }
@@ -138,6 +146,16 @@ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth) {
138
146
  matchingSessions,
139
147
  };
140
148
  }
149
+ function resolveRequestedCurrentAccount(cfg, currentAccount, requestedEnv) {
150
+ const preferredEnv = normalizeRequestedEnv(requestedEnv);
151
+ if (!currentAccount) {
152
+ return null;
153
+ }
154
+ if (!preferredEnv || currentAccount.env === preferredEnv) {
155
+ return currentAccount;
156
+ }
157
+ return (0, config_1.findAccountSession)(currentAccount.username, preferredEnv, cfg);
158
+ }
141
159
  async function resolveAuthenticatedEnvironment(command, actionLabel, options = {}) {
142
160
  const loaded = await loadWorkspaceAwareConfig(command, options);
143
161
  if (!loaded) {
@@ -146,27 +164,37 @@ async function resolveAuthenticatedEnvironment(command, actionLabel, options = {
146
164
  const { cfg, workspaceAuth } = loaded;
147
165
  const currentAccount = (0, config_1.getCurrentAccountSession)(cfg) ?? buildLegacyAccountSession(cfg);
148
166
  const selectedAccount = workspaceAuth
149
- ? resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth).account
150
- : currentAccount;
167
+ ? resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, options.env).account
168
+ : resolveRequestedCurrentAccount(cfg, currentAccount, options.env);
151
169
  if (!selectedAccount) {
152
170
  if (workspaceAuth) {
153
171
  const matchingSessions = (0, config_1.listAccountSessionsForUsername)(workspaceAuth.config.ownerUsername, cfg);
154
- if (!workspaceAuth.config.env && matchingSessions.length > 1) {
172
+ const preferredEnv = normalizeRequestedEnv(options.env) ?? workspaceAuth.config.env;
173
+ if (!preferredEnv && matchingSessions.length > 1) {
155
174
  (0, messages_1.printErrorWithHelp)(`This workspace is pinned to ${workspaceAuth.config.ownerUsername}, but that account is logged in on multiple environments.`, [
156
- `Run "playdrop auth use ${workspaceAuth.config.ownerUsername} --env <env>" to select the matching environment.`,
157
- 'Or add an "env" field to .playdrop.json.',
175
+ `Run "playdrop ${command} --env <env>" to target the matching workspace environment.`,
176
+ `Or run "playdrop auth use ${workspaceAuth.config.ownerUsername} --env <env>" to change your global default account.`,
158
177
  ], { command });
159
178
  process.exitCode = 1;
160
179
  return null;
161
180
  }
162
- const envSuffix = workspaceAuth.config.env ? ` on ${workspaceAuth.config.env}` : '';
181
+ const envSuffix = preferredEnv ? ` on ${preferredEnv}` : '';
163
182
  (0, messages_1.printErrorWithHelp)(`This workspace is pinned to ${workspaceAuth.config.ownerUsername}${envSuffix}, but that account is not logged in.`, [
164
- `Run "playdrop auth login${workspaceAuth.config.env ? ` --env ${workspaceAuth.config.env}` : ''}" while authenticated as ${workspaceAuth.config.ownerUsername}.`,
183
+ `Run "playdrop auth login${preferredEnv ? ` --env ${preferredEnv}` : ''}" while authenticated as ${workspaceAuth.config.ownerUsername}.`,
165
184
  'Or remove .playdrop.json if this workspace should use your current default account.',
166
185
  ], { command });
167
186
  process.exitCode = 1;
168
187
  return null;
169
188
  }
189
+ const preferredEnv = normalizeRequestedEnv(options.env);
190
+ if (currentAccount && preferredEnv) {
191
+ (0, messages_1.printErrorWithHelp)(`${currentAccount.username} is not logged in on ${preferredEnv}.`, [
192
+ `Run "playdrop auth login --env ${preferredEnv}" while authenticated as ${currentAccount.username}.`,
193
+ `Or run "playdrop auth use <username> --env ${preferredEnv}" to switch accounts.`,
194
+ ], { command });
195
+ process.exitCode = 1;
196
+ return null;
197
+ }
170
198
  (0, messages_1.printLoginRequired)(actionLabel, command);
171
199
  process.exitCode = 1;
172
200
  return null;
@@ -203,26 +231,35 @@ async function resolveOptionalEnvironmentContext(command, options = {}) {
203
231
  return null;
204
232
  }
205
233
  const selectedAccount = workspaceAuth
206
- ? resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth).account
207
- : currentAccount;
234
+ ? resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, options.env).account
235
+ : resolveRequestedCurrentAccount(cfg, currentAccount, options.env);
208
236
  if (!selectedAccount) {
209
237
  if (workspaceAuth) {
210
238
  const matchingSessions = (0, config_1.listAccountSessionsForUsername)(workspaceAuth.config.ownerUsername, cfg);
211
- if (!workspaceAuth.config.env && matchingSessions.length > 1) {
239
+ const preferredEnv = normalizeRequestedEnv(options.env) ?? workspaceAuth.config.env;
240
+ if (!preferredEnv && matchingSessions.length > 1) {
212
241
  (0, messages_1.printErrorWithHelp)(`This workspace is pinned to ${workspaceAuth.config.ownerUsername}, but that account is logged in on multiple environments.`, [
213
- `Run "playdrop auth use ${workspaceAuth.config.ownerUsername} --env <env>" to select the matching environment.`,
214
- 'Or add an "env" field to .playdrop.json.',
242
+ `Run "playdrop ${command} --env <env>" to target the matching workspace environment.`,
243
+ `Or run "playdrop auth use ${workspaceAuth.config.ownerUsername} --env <env>" to change your global default account.`,
215
244
  ], { command });
216
245
  }
217
246
  else {
218
- const envSuffix = workspaceAuth.config.env ? ` on ${workspaceAuth.config.env}` : '';
247
+ const envSuffix = preferredEnv ? ` on ${preferredEnv}` : '';
219
248
  (0, messages_1.printErrorWithHelp)(`This workspace is pinned to ${workspaceAuth.config.ownerUsername}${envSuffix}, but that account is not logged in.`, [
220
- `Run "playdrop auth login${workspaceAuth.config.env ? ` --env ${workspaceAuth.config.env}` : ''}" while authenticated as ${workspaceAuth.config.ownerUsername}.`,
249
+ `Run "playdrop auth login${preferredEnv ? ` --env ${preferredEnv}` : ''}" while authenticated as ${workspaceAuth.config.ownerUsername}.`,
221
250
  'Or remove .playdrop.json if this workspace should use your current default account.',
222
251
  ], { command });
223
252
  }
224
253
  process.exitCode = 1;
225
254
  }
255
+ else if (currentAccount && normalizeRequestedEnv(options.env)) {
256
+ const preferredEnv = normalizeRequestedEnv(options.env);
257
+ (0, messages_1.printErrorWithHelp)(`${currentAccount.username} is not logged in on ${preferredEnv}.`, [
258
+ `Run "playdrop auth login --env ${preferredEnv}" while authenticated as ${currentAccount.username}.`,
259
+ `Or run "playdrop auth use <username> --env ${preferredEnv}" to switch accounts.`,
260
+ ], { command });
261
+ process.exitCode = 1;
262
+ }
226
263
  return null;
227
264
  }
228
265
  const envConfig = resolveConfiguredEnvironment(selectedAccount.env, command, {
@@ -455,7 +455,7 @@ async function capture(targetArg, options = {}) {
455
455
  appType: appTypeSlug,
456
456
  devAuth: devOptions.selection.devAuth,
457
457
  player: devOptions.selection.player ? String(devOptions.selection.player) : null,
458
- launchCheck: devOptions.selection.devAuth === 'anonymous',
458
+ launchCheck: true,
459
459
  });
460
460
  const frameUrl = (0, devAuth_1.applyHostedDevAuthSelectionToUrl)(captureBaseUrl, devOptions.selection);
461
461
  console.log(`[capture] Launching Playwright against ${frameUrl}`);
@@ -253,6 +253,7 @@ async function devBrowser(targetArg, options = {}) {
253
253
  appType: appTypeSlug,
254
254
  devAuth: devAuthSelection.devAuth,
255
255
  player: devAuthSelection.player ? String(devAuthSelection.player) : null,
256
+ launchCheck: devAuthSelection.devAuth !== 'prompt',
256
257
  });
257
258
  let launchUrl = devUrl;
258
259
  if (devAuthSelection.devAuth !== 'anonymous') {
@@ -335,6 +336,7 @@ async function devBrowser(targetArg, options = {}) {
335
336
  process.on('SIGTERM', handleSignal);
336
337
  try {
337
338
  context = await (0, playwright_1.launchPersistentChromiumContext)(profileDir, {
339
+ automationOrigin: devAuthSelection.devAuth === 'prompt' ? null : new URL(devUrl).origin,
338
340
  viewport: { width: 1440, height: 960 },
339
341
  });
340
342
  }
@@ -32,6 +32,7 @@ export declare function buildLocalDevAppUrl(input: {
32
32
  appName: string;
33
33
  port?: number;
34
34
  }): string;
35
+ export declare function terminateChildProcessTree(child: ChildProcess | null, timeoutMs?: number): Promise<void>;
35
36
  export declare function ensureDevRouterRunning(port?: number): Promise<void>;
36
37
  export declare function updateMountedDevRuntimeAssetManifest(input: {
37
38
  creatorUsername: string;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEV_ROUTER_PORT = void 0;
7
7
  exports.parseMountConflictError = parseMountConflictError;
8
8
  exports.buildLocalDevAppUrl = buildLocalDevAppUrl;
9
+ exports.terminateChildProcessTree = terminateChildProcessTree;
9
10
  exports.ensureDevRouterRunning = ensureDevRouterRunning;
10
11
  exports.updateMountedDevRuntimeAssetManifest = updateMountedDevRuntimeAssetManifest;
11
12
  exports.startDevServer = startDevServer;
@@ -223,9 +224,64 @@ function spawnDevScript(projectInfo) {
223
224
  cwd: projectInfo.projectDir,
224
225
  stdio: 'inherit',
225
226
  env: { ...process.env },
227
+ detached: process.platform !== 'win32',
226
228
  });
227
229
  return child;
228
230
  }
231
+ function signalProcess(pid, signal, useGroup) {
232
+ const targetPid = useGroup ? -pid : pid;
233
+ try {
234
+ process.kill(targetPid, signal);
235
+ return true;
236
+ }
237
+ catch (error) {
238
+ if (error?.code === 'ESRCH') {
239
+ return false;
240
+ }
241
+ throw error;
242
+ }
243
+ }
244
+ async function waitForChildExit(child, timeoutMs) {
245
+ const pid = child.pid;
246
+ if (!pid || !isPidAlive(pid)) {
247
+ return true;
248
+ }
249
+ return await new Promise((resolvePromise) => {
250
+ let settled = false;
251
+ const onExit = () => {
252
+ if (settled) {
253
+ return;
254
+ }
255
+ settled = true;
256
+ clearTimeout(timeout);
257
+ child.off('exit', onExit);
258
+ resolvePromise(true);
259
+ };
260
+ const timeout = setTimeout(() => {
261
+ if (settled) {
262
+ return;
263
+ }
264
+ settled = true;
265
+ child.off('exit', onExit);
266
+ resolvePromise(!pid || !isPidAlive(pid));
267
+ }, timeoutMs);
268
+ timeout.unref?.();
269
+ child.once('exit', onExit);
270
+ });
271
+ }
272
+ async function terminateChildProcessTree(child, timeoutMs = 2000) {
273
+ const pid = child?.pid;
274
+ if (!child || !pid || !isPidAlive(pid)) {
275
+ return;
276
+ }
277
+ const useGroup = process.platform !== 'win32';
278
+ signalProcess(pid, 'SIGTERM', useGroup);
279
+ if (await waitForChildExit(child, timeoutMs)) {
280
+ return;
281
+ }
282
+ signalProcess(pid, 'SIGKILL', useGroup);
283
+ await waitForChildExit(child, timeoutMs);
284
+ }
229
285
  async function fetchRouterJson(path, init = {}, port = exports.DEV_ROUTER_PORT) {
230
286
  const response = await fetch(`http://${DEV_ROUTER_HOST}:${port}${path}`, init);
231
287
  if (!response.ok) {
@@ -411,9 +467,7 @@ async function startDevServer(options) {
411
467
  await unregisterDevMount(mountId, port);
412
468
  }
413
469
  finally {
414
- if (devProcess && !devProcess.killed) {
415
- devProcess.kill();
416
- }
470
+ await terminateChildProcessTree(devProcess);
417
471
  }
418
472
  },
419
473
  };
@@ -357,7 +357,7 @@ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername
357
357
  sourceAppVersionId,
358
358
  runtimeKey: task.kind === 'owned-asset' ? task.runtimeKey : undefined,
359
359
  tags: task.kind === 'asset' ? task.tags : undefined,
360
- clearTags: task.kind === 'asset' ? options?.clearTags : undefined,
360
+ clearTags: task.kind === 'asset' || task.kind === 'owned-asset' ? options?.clearTags : undefined,
361
361
  shopListed: task.shopListed,
362
362
  shopPriceCredits: task.shopPriceCredits,
363
363
  files,
@@ -374,7 +374,7 @@ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername
374
374
  sourceAppVersionId,
375
375
  runtimeKey: task.kind === 'owned-asset' ? task.runtimeKey : undefined,
376
376
  tags: task.kind === 'asset' ? task.tags : undefined,
377
- clearTags: task.kind === 'asset' ? options?.clearTags : undefined,
377
+ clearTags: task.kind === 'asset' || task.kind === 'owned-asset' ? options?.clearTags : undefined,
378
378
  shopListed: task.shopListed,
379
379
  shopPriceCredits: task.shopPriceCredits,
380
380
  files,
@@ -506,11 +506,14 @@ function buildLocalAssetManifestEntries(preparedLocalAssets) {
506
506
  localAssets.push({
507
507
  uploadKey,
508
508
  name: localAsset.task.name,
509
+ displayName: localAsset.task.displayName,
510
+ description: localAsset.task.description,
509
511
  category: localAsset.task.category,
510
512
  subcategory: localAsset.prepared.subcategory ?? null,
511
513
  format: localAsset.prepared.format,
512
514
  visibility: localAsset.task.visibility,
513
515
  license: localAsset.task.license,
516
+ tags: localAsset.task.tags,
514
517
  shopListed: localAsset.task.shopListed,
515
518
  shopPriceCredits: localAsset.task.shopPriceCredits,
516
519
  files: localAsset.prepared.manifestFiles,
@@ -1,4 +1,5 @@
1
1
  export type UploadCommandOptions = {
2
+ env?: string;
2
3
  skipEcs?: boolean;
3
4
  skipReview?: boolean;
4
5
  clearTags?: boolean;