@playtagon/cli 0.3.4 → 0.4.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/README.md +19 -2
- package/dist/index.js +305 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,23 @@ playtagon logout
|
|
|
30
30
|
|
|
31
31
|
## Uploading Spine Assets
|
|
32
32
|
|
|
33
|
-
###
|
|
33
|
+
### Auto-Upload with File Watcher (Recommended)
|
|
34
|
+
|
|
35
|
+
Start watching a directory for new exports. Files will automatically upload when you export from Spine Editor:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
playtagon spine watch ./exports --studio my-studio --game my-game
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This command runs in the background and automatically uploads files whenever Spine creates a new export. Just keep it running in a terminal while you work.
|
|
42
|
+
|
|
43
|
+
**Options:**
|
|
44
|
+
- `--batch` - Enable batch mode for multiple skeletons sharing one atlas
|
|
45
|
+
- `--interval <seconds>` - Change check interval (default: 2 seconds)
|
|
46
|
+
|
|
47
|
+
Press `Ctrl+C` to stop watching.
|
|
48
|
+
|
|
49
|
+
### Manual Upload
|
|
34
50
|
|
|
35
51
|
Upload a Spine asset directory to a specific studio and game:
|
|
36
52
|
|
|
@@ -157,7 +173,8 @@ The CLI tracks synced assets in `.sync-manifest.json`. Subsequent syncs only dow
|
|
|
157
173
|
| `playtagon whoami` | Show current user info |
|
|
158
174
|
| `playtagon setup` | Interactive setup wizard |
|
|
159
175
|
| `playtagon config` | Manage configuration |
|
|
160
|
-
| `playtagon spine upload` | Upload Spine assets |
|
|
176
|
+
| `playtagon spine upload` | Upload Spine assets (manual) |
|
|
177
|
+
| `playtagon spine watch` | Auto-upload on file changes (recommended) |
|
|
161
178
|
| `playtagon spine validate` | Validate Spine files |
|
|
162
179
|
| `playtagon spine preset` | Output export preset JSON |
|
|
163
180
|
| `playtagon spine sync` | Sync approved assets with codegen |
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command12 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/login.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -244,7 +244,7 @@ async function loginWithEmail(email, password) {
|
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
async function loginWithBrowser() {
|
|
247
|
-
return new Promise((
|
|
247
|
+
return new Promise((resolve6) => {
|
|
248
248
|
const port = 19419;
|
|
249
249
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
250
250
|
const codeVerifier = generateCodeVerifier();
|
|
@@ -274,7 +274,7 @@ async function loginWithBrowser() {
|
|
|
274
274
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
275
275
|
res.end(getErrorHtml(errorDescription || error));
|
|
276
276
|
cleanup();
|
|
277
|
-
|
|
277
|
+
resolve6({ success: false, error: errorDescription || error });
|
|
278
278
|
return;
|
|
279
279
|
}
|
|
280
280
|
const accessToken = url.searchParams.get("access_token");
|
|
@@ -288,7 +288,7 @@ async function loginWithBrowser() {
|
|
|
288
288
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
289
289
|
res.end(getSuccessHtml(""));
|
|
290
290
|
cleanup();
|
|
291
|
-
|
|
291
|
+
resolve6({ success: true });
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
294
|
const code = url.searchParams.get("code");
|
|
@@ -300,7 +300,7 @@ async function loginWithBrowser() {
|
|
|
300
300
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
301
301
|
res.end(getErrorHtml(exchangeError?.message || "Failed to exchange code"));
|
|
302
302
|
cleanup();
|
|
303
|
-
|
|
303
|
+
resolve6({ success: false, error: exchangeError?.message || "Failed to exchange code" });
|
|
304
304
|
return;
|
|
305
305
|
}
|
|
306
306
|
credentials.save({
|
|
@@ -313,19 +313,19 @@ async function loginWithBrowser() {
|
|
|
313
313
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
314
314
|
res.end(getSuccessHtml(data.user?.email || ""));
|
|
315
315
|
cleanup();
|
|
316
|
-
|
|
316
|
+
resolve6({ success: true });
|
|
317
317
|
} catch (err) {
|
|
318
318
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
319
319
|
res.end(getErrorHtml(err instanceof Error ? err.message : "Unknown error"));
|
|
320
320
|
cleanup();
|
|
321
|
-
|
|
321
|
+
resolve6({ success: false, error: err instanceof Error ? err.message : "Unknown error" });
|
|
322
322
|
}
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
325
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
326
326
|
res.end(getErrorHtml("No authorization token received"));
|
|
327
327
|
cleanup();
|
|
328
|
-
|
|
328
|
+
resolve6({ success: false, error: "No authorization token received" });
|
|
329
329
|
} else {
|
|
330
330
|
res.writeHead(404);
|
|
331
331
|
res.end("Not found");
|
|
@@ -337,15 +337,15 @@ async function loginWithBrowser() {
|
|
|
337
337
|
server.on("error", (err) => {
|
|
338
338
|
if (err.code === "EADDRINUSE") {
|
|
339
339
|
cleanup();
|
|
340
|
-
|
|
340
|
+
resolve6({ success: false, error: `Port ${port} is already in use. Please close any applications using it.` });
|
|
341
341
|
} else {
|
|
342
342
|
cleanup();
|
|
343
|
-
|
|
343
|
+
resolve6({ success: false, error: err.message });
|
|
344
344
|
}
|
|
345
345
|
});
|
|
346
346
|
timeoutId = setTimeout(() => {
|
|
347
347
|
cleanup();
|
|
348
|
-
|
|
348
|
+
resolve6({ success: false, error: "Authentication timed out. Please try again." });
|
|
349
349
|
}, 5 * 60 * 1e3);
|
|
350
350
|
});
|
|
351
351
|
}
|
|
@@ -497,8 +497,8 @@ async function promptEmailLogin() {
|
|
|
497
497
|
input: process.stdin,
|
|
498
498
|
output: process.stdout
|
|
499
499
|
});
|
|
500
|
-
const question = (prompt) => new Promise((
|
|
501
|
-
rl.question(prompt,
|
|
500
|
+
const question = (prompt) => new Promise((resolve6) => {
|
|
501
|
+
rl.question(prompt, resolve6);
|
|
502
502
|
});
|
|
503
503
|
try {
|
|
504
504
|
const email = await question("Email: ");
|
|
@@ -590,7 +590,7 @@ async function browserLogin() {
|
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
592
|
function questionHidden(prompt, rl) {
|
|
593
|
-
return new Promise((
|
|
593
|
+
return new Promise((resolve6) => {
|
|
594
594
|
const stdin = process.stdin;
|
|
595
595
|
const stdout = process.stdout;
|
|
596
596
|
stdout.write(prompt);
|
|
@@ -610,7 +610,7 @@ function questionHidden(prompt, rl) {
|
|
|
610
610
|
}
|
|
611
611
|
stdin.removeListener("data", onData);
|
|
612
612
|
stdout.write("\n");
|
|
613
|
-
|
|
613
|
+
resolve6(password);
|
|
614
614
|
break;
|
|
615
615
|
case "":
|
|
616
616
|
process.exit(1);
|
|
@@ -682,7 +682,7 @@ var whoamiCommand = new Command3("whoami").description("Show current logged in u
|
|
|
682
682
|
});
|
|
683
683
|
|
|
684
684
|
// src/commands/spine/index.ts
|
|
685
|
-
import { Command as
|
|
685
|
+
import { Command as Command9 } from "commander";
|
|
686
686
|
|
|
687
687
|
// src/commands/spine/validate.ts
|
|
688
688
|
import { Command as Command4 } from "commander";
|
|
@@ -1937,26 +1937,273 @@ function pascalCase2(str) {
|
|
|
1937
1937
|
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
1938
1938
|
}
|
|
1939
1939
|
|
|
1940
|
+
// src/commands/spine/watch.ts
|
|
1941
|
+
import { Command as Command8 } from "commander";
|
|
1942
|
+
import * as fs5 from "fs";
|
|
1943
|
+
import * as path6 from "path";
|
|
1944
|
+
import ora6 from "ora";
|
|
1945
|
+
var watchCommand = new Command8("watch").description("Watch directory for new Spine exports and auto-upload").argument("<directory>", "Directory to watch for Spine files").option("-s, --studio <studio>", "Studio ID or slug (uses default if set)").option("-g, --game <game>", "Game ID or slug (uses default if set)").option("--batch", "Enable batch mode for multiple skeletons sharing atlas").option("--interval <seconds>", "Check interval in seconds", "2").action(async (directory, options) => {
|
|
1946
|
+
if (!credentials.isLoggedIn()) {
|
|
1947
|
+
logger.error("Not logged in.");
|
|
1948
|
+
logger.info(`Run ${logger.command("playtagon login")} first.`);
|
|
1949
|
+
process.exit(1);
|
|
1950
|
+
}
|
|
1951
|
+
const studioOption = options.studio || config.defaultStudio;
|
|
1952
|
+
const gameOption = options.game || config.defaultGame;
|
|
1953
|
+
if (!studioOption) {
|
|
1954
|
+
logger.error("Studio is required.");
|
|
1955
|
+
logger.info(`Either provide ${logger.command("--studio <slug>")} or set a default:`);
|
|
1956
|
+
logger.info(` ${logger.command("playtagon config --studio <slug>")}`);
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
const absolutePath = path6.resolve(directory);
|
|
1960
|
+
if (!fs5.existsSync(absolutePath)) {
|
|
1961
|
+
logger.error(`Directory not found: ${absolutePath}`);
|
|
1962
|
+
process.exit(1);
|
|
1963
|
+
}
|
|
1964
|
+
const spinner = ora6("Resolving studio...").start();
|
|
1965
|
+
const studio = await resolveStudio(studioOption);
|
|
1966
|
+
if (!studio) {
|
|
1967
|
+
spinner.fail("Studio not found");
|
|
1968
|
+
logger.error(`Studio "${studioOption}" not found or you don't have access.`);
|
|
1969
|
+
process.exit(1);
|
|
1970
|
+
}
|
|
1971
|
+
spinner.succeed(`Studio: ${studio.name}`);
|
|
1972
|
+
let game = null;
|
|
1973
|
+
if (gameOption) {
|
|
1974
|
+
spinner.start("Resolving game...");
|
|
1975
|
+
game = await resolveGame(studio.id, gameOption);
|
|
1976
|
+
if (!game) {
|
|
1977
|
+
spinner.fail("Game not found");
|
|
1978
|
+
logger.error(`Game "${gameOption}" not found in studio "${studio.name}".`);
|
|
1979
|
+
process.exit(1);
|
|
1980
|
+
}
|
|
1981
|
+
spinner.succeed(`Game: ${game.name}`);
|
|
1982
|
+
}
|
|
1983
|
+
const state = {
|
|
1984
|
+
lastModified: /* @__PURE__ */ new Map(),
|
|
1985
|
+
isUploading: false,
|
|
1986
|
+
studioId: studio.id,
|
|
1987
|
+
gameId: game?.id
|
|
1988
|
+
};
|
|
1989
|
+
await scanDirectory(absolutePath, state);
|
|
1990
|
+
console.log();
|
|
1991
|
+
logger.header("Watching for Spine Exports");
|
|
1992
|
+
logger.item("Directory", absolutePath);
|
|
1993
|
+
logger.item("Studio", studio.name);
|
|
1994
|
+
if (game) logger.item("Game", game.name);
|
|
1995
|
+
logger.item("Batch Mode", options.batch ? "Yes" : "No");
|
|
1996
|
+
logger.item("Check Interval", `${options.interval}s`);
|
|
1997
|
+
console.log();
|
|
1998
|
+
logger.info("Watching for changes... (Press Ctrl+C to stop)");
|
|
1999
|
+
console.log();
|
|
2000
|
+
const intervalMs = parseInt(options.interval) * 1e3;
|
|
2001
|
+
setInterval(async () => {
|
|
2002
|
+
await checkForChanges(absolutePath, state, options.batch);
|
|
2003
|
+
}, intervalMs);
|
|
2004
|
+
process.on("SIGINT", () => {
|
|
2005
|
+
console.log();
|
|
2006
|
+
logger.success("Stopped watching.");
|
|
2007
|
+
process.exit(0);
|
|
2008
|
+
});
|
|
2009
|
+
});
|
|
2010
|
+
async function scanDirectory(directory, state) {
|
|
2011
|
+
const files = getAllFiles(directory);
|
|
2012
|
+
for (const file of files) {
|
|
2013
|
+
const stats = fs5.statSync(file);
|
|
2014
|
+
state.lastModified.set(file, stats.mtimeMs);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
function getAllFiles(directory) {
|
|
2018
|
+
const files = [];
|
|
2019
|
+
try {
|
|
2020
|
+
const entries = fs5.readdirSync(directory, { withFileTypes: true });
|
|
2021
|
+
for (const entry of entries) {
|
|
2022
|
+
const fullPath = path6.join(directory, entry.name);
|
|
2023
|
+
if (entry.isFile()) {
|
|
2024
|
+
const ext = path6.extname(entry.name).toLowerCase();
|
|
2025
|
+
if ([".json", ".skel", ".atlas", ".png", ".jpg", ".jpeg", ".webp"].includes(ext)) {
|
|
2026
|
+
files.push(fullPath);
|
|
2027
|
+
}
|
|
2028
|
+
} else if (entry.isDirectory()) {
|
|
2029
|
+
files.push(...getAllFiles(fullPath));
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
} catch (error) {
|
|
2033
|
+
}
|
|
2034
|
+
return files;
|
|
2035
|
+
}
|
|
2036
|
+
async function checkForChanges(directory, state, batchMode) {
|
|
2037
|
+
if (state.isUploading) {
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
const files = getAllFiles(directory);
|
|
2041
|
+
let hasChanges = false;
|
|
2042
|
+
let newestChangeTime = 0;
|
|
2043
|
+
for (const file of files) {
|
|
2044
|
+
try {
|
|
2045
|
+
const stats = fs5.statSync(file);
|
|
2046
|
+
const lastModified = state.lastModified.get(file) || 0;
|
|
2047
|
+
if (stats.mtimeMs > lastModified) {
|
|
2048
|
+
hasChanges = true;
|
|
2049
|
+
newestChangeTime = Math.max(newestChangeTime, stats.mtimeMs);
|
|
2050
|
+
}
|
|
2051
|
+
} catch (error) {
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
if (!hasChanges) {
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
const timeSinceLastChange = Date.now() - newestChangeTime;
|
|
2058
|
+
if (timeSinceLastChange < 1e3) {
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
for (const file of files) {
|
|
2062
|
+
try {
|
|
2063
|
+
const stats = fs5.statSync(file);
|
|
2064
|
+
state.lastModified.set(file, stats.mtimeMs);
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
state.isUploading = true;
|
|
2069
|
+
await uploadDirectory(directory, state, batchMode);
|
|
2070
|
+
state.isUploading = false;
|
|
2071
|
+
}
|
|
2072
|
+
async function uploadDirectory(directory, state, batchMode) {
|
|
2073
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
2074
|
+
console.log(`[${timestamp}] Changes detected, uploading...`);
|
|
2075
|
+
const spinner = ora6("Validating files...").start();
|
|
2076
|
+
let validation;
|
|
2077
|
+
try {
|
|
2078
|
+
validation = validateDirectory(directory, batchMode);
|
|
2079
|
+
} catch (error) {
|
|
2080
|
+
spinner.fail("Validation failed");
|
|
2081
|
+
logger.error(error instanceof Error ? error.message : "Unknown error");
|
|
2082
|
+
console.log();
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (!validation.isValid) {
|
|
2086
|
+
spinner.fail("Validation failed");
|
|
2087
|
+
const errors = validation.issues.filter((i) => i.type === "error");
|
|
2088
|
+
for (const error of errors) {
|
|
2089
|
+
logger.validationError(
|
|
2090
|
+
error.file ? `${error.file}: ${error.message}` : error.message
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
console.log();
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
spinner.text = `Validated ${validation.assets.length} asset(s)`;
|
|
2097
|
+
spinner.succeed();
|
|
2098
|
+
const assetName = path6.basename(directory);
|
|
2099
|
+
const slug = generateSlug(assetName);
|
|
2100
|
+
if (!validateSlug(slug)) {
|
|
2101
|
+
logger.error(`Invalid slug generated: "${slug}". Skipping upload.`);
|
|
2102
|
+
console.log();
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
spinner.start("Uploading...");
|
|
2106
|
+
try {
|
|
2107
|
+
const formData = await buildFormData2(validation, {
|
|
2108
|
+
studioId: state.studioId,
|
|
2109
|
+
gameId: state.gameId,
|
|
2110
|
+
name: assetName,
|
|
2111
|
+
slug,
|
|
2112
|
+
batchMode: validation.isBatchMode
|
|
2113
|
+
});
|
|
2114
|
+
const result = await uploadSpineAsset(formData);
|
|
2115
|
+
if (result.success) {
|
|
2116
|
+
spinner.succeed("Upload complete!");
|
|
2117
|
+
if (result.assets && result.assets.length > 1) {
|
|
2118
|
+
console.log(` Uploaded ${result.assets.length} assets (batch mode)`);
|
|
2119
|
+
for (const asset of result.assets) {
|
|
2120
|
+
console.log(` ${asset.name} (${asset.slug}) - ${asset.status}`);
|
|
2121
|
+
}
|
|
2122
|
+
} else if (result.asset) {
|
|
2123
|
+
console.log(` ${result.asset.name} (${result.asset.slug}) - ${result.asset.status}`);
|
|
2124
|
+
}
|
|
2125
|
+
console.log();
|
|
2126
|
+
} else {
|
|
2127
|
+
spinner.fail("Upload failed");
|
|
2128
|
+
logger.error(result.error || "Unknown error");
|
|
2129
|
+
console.log();
|
|
2130
|
+
}
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
spinner.fail("Upload failed");
|
|
2133
|
+
logger.error(error instanceof Error ? error.message : "Unknown error");
|
|
2134
|
+
console.log();
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
async function buildFormData2(validation, options) {
|
|
2138
|
+
const formData = new FormData();
|
|
2139
|
+
formData.append("studioId", options.studioId);
|
|
2140
|
+
if (options.gameId) formData.append("gameId", options.gameId);
|
|
2141
|
+
formData.append("name", options.name);
|
|
2142
|
+
formData.append("slug", options.slug);
|
|
2143
|
+
if (options.batchMode) formData.append("batchMode", "true");
|
|
2144
|
+
const filesAdded = /* @__PURE__ */ new Set();
|
|
2145
|
+
for (const asset of validation.assets) {
|
|
2146
|
+
const filePath = asset.skeleton.path;
|
|
2147
|
+
if (!filesAdded.has(filePath)) {
|
|
2148
|
+
const buffer = fs5.readFileSync(filePath);
|
|
2149
|
+
const blob = new Blob([buffer]);
|
|
2150
|
+
formData.append("files", blob, getBaseName(filePath));
|
|
2151
|
+
filesAdded.add(filePath);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
if (validation.sharedAtlas && !filesAdded.has(validation.sharedAtlas)) {
|
|
2155
|
+
const buffer = fs5.readFileSync(validation.sharedAtlas);
|
|
2156
|
+
const blob = new Blob([buffer]);
|
|
2157
|
+
formData.append("files", blob, getBaseName(validation.sharedAtlas));
|
|
2158
|
+
filesAdded.add(validation.sharedAtlas);
|
|
2159
|
+
}
|
|
2160
|
+
for (const texturePath of validation.sharedTextures) {
|
|
2161
|
+
if (!filesAdded.has(texturePath)) {
|
|
2162
|
+
const buffer = fs5.readFileSync(texturePath);
|
|
2163
|
+
const blob = new Blob([buffer]);
|
|
2164
|
+
formData.append("files", blob, getBaseName(texturePath));
|
|
2165
|
+
filesAdded.add(texturePath);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
for (const asset of validation.assets) {
|
|
2169
|
+
if (asset.atlasPath && !filesAdded.has(asset.atlasPath)) {
|
|
2170
|
+
const buffer = fs5.readFileSync(asset.atlasPath);
|
|
2171
|
+
const blob = new Blob([buffer]);
|
|
2172
|
+
formData.append("files", blob, getBaseName(asset.atlasPath));
|
|
2173
|
+
filesAdded.add(asset.atlasPath);
|
|
2174
|
+
}
|
|
2175
|
+
for (const texturePath of asset.texturePaths) {
|
|
2176
|
+
if (!filesAdded.has(texturePath)) {
|
|
2177
|
+
const buffer = fs5.readFileSync(texturePath);
|
|
2178
|
+
const blob = new Blob([buffer]);
|
|
2179
|
+
formData.append("files", blob, getBaseName(texturePath));
|
|
2180
|
+
filesAdded.add(texturePath);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
return formData;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1940
2187
|
// src/commands/spine/index.ts
|
|
1941
|
-
var spineCommand = new
|
|
2188
|
+
var spineCommand = new Command9("spine").description("Manage Spine animation assets").addCommand(validateCommand).addCommand(uploadCommand).addCommand(watchCommand).addCommand(presetCommand).addCommand(syncCommand);
|
|
1942
2189
|
|
|
1943
2190
|
// src/commands/setup.ts
|
|
1944
|
-
import { Command as
|
|
1945
|
-
import * as
|
|
1946
|
-
import * as
|
|
2191
|
+
import { Command as Command10 } from "commander";
|
|
2192
|
+
import * as fs6 from "fs";
|
|
2193
|
+
import * as path7 from "path";
|
|
1947
2194
|
import * as os from "os";
|
|
1948
2195
|
import { execFile } from "child_process";
|
|
1949
|
-
import
|
|
1950
|
-
var PLAYTAGON_DIR =
|
|
1951
|
-
var setupCommand = new
|
|
2196
|
+
import ora7 from "ora";
|
|
2197
|
+
var PLAYTAGON_DIR = path7.join(os.homedir(), ".playtagon");
|
|
2198
|
+
var setupCommand = new Command10("setup").description("Set up integrations").addCommand(spineIntegrationCommand());
|
|
1952
2199
|
function spineIntegrationCommand() {
|
|
1953
|
-
return new
|
|
2200
|
+
return new Command10("spine-integration").description("Set up automatic Spine Editor to Platform upload").option("-s, --studio <studio>", "Default studio for uploads").option("-g, --game <game>", "Default game for uploads").option("--force", "Overwrite existing setup files").action(async (options) => {
|
|
1954
2201
|
if (!credentials.isLoggedIn()) {
|
|
1955
2202
|
logger.error("Not logged in.");
|
|
1956
2203
|
logger.info(`Run ${logger.command("playtagon login")} first.`);
|
|
1957
2204
|
process.exit(1);
|
|
1958
2205
|
}
|
|
1959
|
-
const spinner =
|
|
2206
|
+
const spinner = ora7("Checking authentication...").start();
|
|
1960
2207
|
const user = await getCurrentUser();
|
|
1961
2208
|
if (!user) {
|
|
1962
2209
|
spinner.fail("Session expired");
|
|
@@ -1988,8 +2235,8 @@ function spineIntegrationCommand() {
|
|
|
1988
2235
|
}
|
|
1989
2236
|
}
|
|
1990
2237
|
spinner.start("Creating setup files...");
|
|
1991
|
-
if (!
|
|
1992
|
-
|
|
2238
|
+
if (!fs6.existsSync(PLAYTAGON_DIR)) {
|
|
2239
|
+
fs6.mkdirSync(PLAYTAGON_DIR, { recursive: true, mode: 448 });
|
|
1993
2240
|
}
|
|
1994
2241
|
const scriptPaths = generatePostExportScripts(studioSlug, options.game, options.force);
|
|
1995
2242
|
const presetPath = generateExportPreset(scriptPaths.sh, options.force);
|
|
@@ -2058,8 +2305,8 @@ function spineIntegrationCommand() {
|
|
|
2058
2305
|
});
|
|
2059
2306
|
}
|
|
2060
2307
|
function generatePostExportScripts(studioSlug, gameSlug, force = false) {
|
|
2061
|
-
const shPath =
|
|
2062
|
-
const batPath =
|
|
2308
|
+
const shPath = path7.join(PLAYTAGON_DIR, "upload.sh");
|
|
2309
|
+
const batPath = path7.join(PLAYTAGON_DIR, "upload.bat");
|
|
2063
2310
|
let uploadCmd = `playtagon spine upload "$1" --studio ${studioSlug}`;
|
|
2064
2311
|
if (gameSlug) {
|
|
2065
2312
|
uploadCmd += ` --game ${gameSlug}`;
|
|
@@ -2107,39 +2354,50 @@ REM This script is called by Spine Editor after export.
|
|
|
2107
2354
|
REM It uploads the exported files to Playtagon Platform.
|
|
2108
2355
|
|
|
2109
2356
|
set EXPORT_DIR=%~1
|
|
2357
|
+
set LOG_FILE=%USERPROFILE%\\.playtagon\\upload.log
|
|
2358
|
+
|
|
2359
|
+
REM Log script execution
|
|
2360
|
+
echo [%date% %time%] Script started with directory: %EXPORT_DIR% >> "%LOG_FILE%"
|
|
2110
2361
|
|
|
2111
2362
|
if "%EXPORT_DIR%"=="" (
|
|
2112
2363
|
echo Error: No export directory provided
|
|
2364
|
+
echo [%date% %time%] ERROR: No export directory provided >> "%LOG_FILE%"
|
|
2113
2365
|
exit /b 1
|
|
2114
2366
|
)
|
|
2115
2367
|
|
|
2116
2368
|
echo Uploading to Playtagon...
|
|
2117
|
-
|
|
2369
|
+
echo [%date% %time%] Running upload command... >> "%LOG_FILE%"
|
|
2370
|
+
${uploadCmdWin} >> "%LOG_FILE%" 2>&1
|
|
2118
2371
|
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2372
|
+
if %ERRORLEVEL% EQU 0 (
|
|
2373
|
+
echo [%date% %time%] Upload successful >> "%LOG_FILE%"
|
|
2374
|
+
REM Windows notification (PowerShell)
|
|
2375
|
+
powershell -Command "& {Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('Spine export uploaded successfully', 'Playtagon', 'OK', 'Information')}" 2>nul
|
|
2376
|
+
echo Upload complete!
|
|
2377
|
+
) else (
|
|
2378
|
+
echo [%date% %time%] Upload failed with error code %ERRORLEVEL% >> "%LOG_FILE%"
|
|
2379
|
+
powershell -Command "& {Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('Upload failed! Check log file', 'Playtagon Error', 'OK', 'Error')}" 2>nul
|
|
2380
|
+
)
|
|
2123
2381
|
`;
|
|
2124
|
-
if (!
|
|
2125
|
-
|
|
2382
|
+
if (!fs6.existsSync(shPath) || force) {
|
|
2383
|
+
fs6.writeFileSync(shPath, shScript, { mode: 493 });
|
|
2126
2384
|
} else {
|
|
2127
2385
|
logger.warn(`${shPath} already exists. Use --force to overwrite.`);
|
|
2128
2386
|
}
|
|
2129
|
-
if (!
|
|
2130
|
-
|
|
2387
|
+
if (!fs6.existsSync(batPath) || force) {
|
|
2388
|
+
fs6.writeFileSync(batPath, batScript);
|
|
2131
2389
|
}
|
|
2132
2390
|
return { sh: shPath, bat: batPath };
|
|
2133
2391
|
}
|
|
2134
2392
|
function generateExportPreset(scriptPath, force = false) {
|
|
2135
|
-
const presetPath =
|
|
2393
|
+
const presetPath = path7.join(PLAYTAGON_DIR, "playtagon-spine-preset.export.json");
|
|
2136
2394
|
const preset = {
|
|
2137
2395
|
...SPINE_EXPORT_PRESET,
|
|
2138
2396
|
name: "Upload to Playtagon",
|
|
2139
2397
|
postScript: process.platform === "win32" ? `"${scriptPath.replace(".sh", ".bat")}" "{output}"` : `"${scriptPath}" "{output}"`
|
|
2140
2398
|
};
|
|
2141
|
-
if (!
|
|
2142
|
-
|
|
2399
|
+
if (!fs6.existsSync(presetPath) || force) {
|
|
2400
|
+
fs6.writeFileSync(presetPath, JSON.stringify(preset, null, 2));
|
|
2143
2401
|
} else {
|
|
2144
2402
|
logger.warn(`${presetPath} already exists. Use --force to overwrite.`);
|
|
2145
2403
|
}
|
|
@@ -2167,9 +2425,9 @@ function openFolder(folderPath) {
|
|
|
2167
2425
|
}
|
|
2168
2426
|
|
|
2169
2427
|
// src/commands/config.ts
|
|
2170
|
-
import { Command as
|
|
2171
|
-
import
|
|
2172
|
-
var configCommand = new
|
|
2428
|
+
import { Command as Command11 } from "commander";
|
|
2429
|
+
import ora8 from "ora";
|
|
2430
|
+
var configCommand = new Command11("config").description("View or set CLI configuration").option("-s, --studio <studio>", "Set default studio").option("-g, --game <game>", "Set default game").option("--clear", "Clear all default settings").action(async (options) => {
|
|
2173
2431
|
if (options.clear) {
|
|
2174
2432
|
config.defaultStudio = void 0;
|
|
2175
2433
|
config.defaultGame = void 0;
|
|
@@ -2182,7 +2440,7 @@ var configCommand = new Command10("config").description("View or set CLI configu
|
|
|
2182
2440
|
logger.info(`Run ${logger.command("playtagon login")} first.`);
|
|
2183
2441
|
process.exit(1);
|
|
2184
2442
|
}
|
|
2185
|
-
const spinner =
|
|
2443
|
+
const spinner = ora8("Verifying studio...").start();
|
|
2186
2444
|
const studios = await getStudios();
|
|
2187
2445
|
const studio = studios.find(
|
|
2188
2446
|
(s) => s.slug === options.studio || s.id === options.studio || s.name === options.studio
|
|
@@ -2212,7 +2470,7 @@ var configCommand = new Command10("config").description("View or set CLI configu
|
|
|
2212
2470
|
logger.info(`Either provide ${logger.command("--studio <slug>")} or set a default studio first.`);
|
|
2213
2471
|
process.exit(1);
|
|
2214
2472
|
}
|
|
2215
|
-
const spinner =
|
|
2473
|
+
const spinner = ora8("Verifying game...").start();
|
|
2216
2474
|
const studios = await getStudios();
|
|
2217
2475
|
const studio = studios.find(
|
|
2218
2476
|
(s) => s.slug === studioSlug || s.id === studioSlug || s.name === studioSlug
|
|
@@ -2272,7 +2530,7 @@ var configCommand = new Command10("config").description("View or set CLI configu
|
|
|
2272
2530
|
});
|
|
2273
2531
|
|
|
2274
2532
|
// src/index.ts
|
|
2275
|
-
var program = new
|
|
2533
|
+
var program = new Command12();
|
|
2276
2534
|
program.name("playtagon").description("Playtagon CLI - Upload and manage game assets").version("0.3.0");
|
|
2277
2535
|
program.addCommand(loginCommand);
|
|
2278
2536
|
program.addCommand(logoutCommand);
|