codeplay-common 4.4.9 → 4.5.1
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.
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { createHash } = require('node:crypto');
|
|
8
|
-
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { createHash } = require('node:crypto');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
const { createInterface } = require('node:readline/promises');
|
|
9
10
|
|
|
10
11
|
const projectDirectory = __dirname;
|
|
11
12
|
const capacitorConfigPath = path.join(projectDirectory, 'capacitor.config.json');
|
|
@@ -31,9 +32,9 @@ const appFilePrefix = `${appUniqueId}. ${sanitizeFileNamePart(appName)}`;
|
|
|
31
32
|
const playStoreDownloadUrl = `https://play.google.com/store/apps/details?id=${encodeURIComponent(packageId)}`;
|
|
32
33
|
const configuredAppStoreUrl = String(iosStoreConfig.appStoreUrl || '')
|
|
33
34
|
.match(/https:\/\/apps\.apple\.com\/app\/id\d+/)?.[0] || '';
|
|
34
|
-
|
|
35
|
-
? configuredAppStoreUrl
|
|
36
|
-
: '';
|
|
35
|
+
let appStoreDownloadUrl = /^https:\/\/apps\.apple\.com\/app\/id\d+$/.test(configuredAppStoreUrl)
|
|
36
|
+
? configuredAppStoreUrl
|
|
37
|
+
: '';
|
|
37
38
|
const promoDesignIndex = createHash('sha1').update(`${appUniqueId}:${appName}`).digest()[0] % 4;
|
|
38
39
|
const promoDesigns = [
|
|
39
40
|
{ accent: '0x23d5c3', background: '0x07101f', panelX: 105, panelWidth: 500, phoneX: 134 },
|
|
@@ -162,7 +163,7 @@ const neuralVoiceCatalog = {
|
|
|
162
163
|
label: 'Clara — Canadian English, clear and friendly',
|
|
163
164
|
},
|
|
164
165
|
};
|
|
165
|
-
function sanitizeFileNamePart(value) {
|
|
166
|
+
function sanitizeFileNamePart(value) {
|
|
166
167
|
return String(value)
|
|
167
168
|
.trim()
|
|
168
169
|
.replace(/[<>:"/\\|?*\u0000-\u001F]/g, '_')
|
|
@@ -204,6 +205,27 @@ async function ensureEndCardBanner() {
|
|
|
204
205
|
+ 'Then run the video-generation command again.',
|
|
205
206
|
);
|
|
206
207
|
}
|
|
208
|
+
|
|
209
|
+
async function ensureAppStoreDownloadUrl() {
|
|
210
|
+
if (appStoreDownloadUrl) return;
|
|
211
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
212
|
+
try {
|
|
213
|
+
const appStoreId = (await readline.question(
|
|
214
|
+
'Enter the iOS App Store ID (press Enter to skip): ',
|
|
215
|
+
)).trim();
|
|
216
|
+
if (!appStoreId) return;
|
|
217
|
+
if (!/^\d+$/.test(appStoreId)) {
|
|
218
|
+
throw new Error('iOS App Store ID must contain digits only.');
|
|
219
|
+
}
|
|
220
|
+
appStoreDownloadUrl = `https://apps.apple.com/app/id${appStoreId}`;
|
|
221
|
+
iosStoreConfig = { ...iosStoreConfig, appStoreUrl: appStoreDownloadUrl };
|
|
222
|
+
fs.mkdirSync(path.dirname(iosStoreConfigPath), { recursive: true });
|
|
223
|
+
fs.writeFileSync(iosStoreConfigPath, `${JSON.stringify(iosStoreConfig, null, 2)}\n`, 'utf8');
|
|
224
|
+
console.log(`Saved iOS App Store URL to ${iosStoreConfigPath}`);
|
|
225
|
+
} finally {
|
|
226
|
+
readline.close();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
207
229
|
|
|
208
230
|
function findVideos(directory) {
|
|
209
231
|
if (!fs.existsSync(directory)) return [];
|
|
@@ -723,13 +745,55 @@ function createCopyFingerprint(appContext, inputPaths) {
|
|
|
723
745
|
})).digest('hex');
|
|
724
746
|
}
|
|
725
747
|
|
|
726
|
-
function
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
748
|
+
function getCodexCandidates() {
|
|
749
|
+
const candidates = ['codex'];
|
|
750
|
+
const configuredCodexPath = String(process.env.CODEX_BIN || '').trim();
|
|
751
|
+
if (configuredCodexPath) candidates.unshift(configuredCodexPath);
|
|
752
|
+
|
|
753
|
+
if (process.platform === 'win32') {
|
|
754
|
+
const userProfile = process.env.USERPROFILE || process.env.HOME;
|
|
755
|
+
const extensionRoots = [
|
|
756
|
+
path.join(userProfile || '', '.vscode', 'extensions'),
|
|
757
|
+
path.join(userProfile || '', '.vscode-insiders', 'extensions'),
|
|
758
|
+
path.join(userProfile || '', '.vscode-server', 'extensions'),
|
|
759
|
+
path.join(userProfile || '', '.vscode-server-insiders', 'extensions'),
|
|
760
|
+
];
|
|
761
|
+
const executableNames = ['codex.exe', 'codex'];
|
|
762
|
+
|
|
763
|
+
for (const extensionRoot of extensionRoots) {
|
|
764
|
+
if (!fs.existsSync(extensionRoot)) continue;
|
|
765
|
+
for (const extensionName of fs.readdirSync(extensionRoot)) {
|
|
766
|
+
if (!/^openai\.chatgpt-/i.test(extensionName)) continue;
|
|
767
|
+
const extensionPath = path.join(extensionRoot, extensionName, 'bin');
|
|
768
|
+
if (!fs.existsSync(extensionPath)) continue;
|
|
769
|
+
for (const platformDirectory of fs.readdirSync(extensionPath)) {
|
|
770
|
+
for (const executableName of executableNames) {
|
|
771
|
+
candidates.push(path.join(extensionPath, platformDirectory, executableName));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return [...new Set(candidates)];
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function getCodexExecutable() {
|
|
782
|
+
for (const candidate of getCodexCandidates()) {
|
|
783
|
+
const versionResult = spawnSync(candidate, ['--version'], {
|
|
784
|
+
encoding: 'utf8',
|
|
785
|
+
windowsHide: true,
|
|
786
|
+
});
|
|
787
|
+
if (versionResult.error || versionResult.status !== 0) continue;
|
|
788
|
+
|
|
789
|
+
const loginResult = spawnSync(candidate, ['login', 'status'], {
|
|
790
|
+
encoding: 'utf8',
|
|
791
|
+
windowsHide: true,
|
|
792
|
+
});
|
|
793
|
+
if (!loginResult.error && loginResult.status === 0) return candidate;
|
|
794
|
+
}
|
|
795
|
+
return '';
|
|
796
|
+
}
|
|
733
797
|
|
|
734
798
|
function detectActiveVideoCrop(ffmpegCommand, inputPath, videoDetails) {
|
|
735
799
|
const videoStream = videoDetails.streams?.find((stream) => stream.codec_type === 'video') || {};
|
|
@@ -912,12 +976,11 @@ function validatePromoCopy(value) {
|
|
|
912
976
|
}
|
|
913
977
|
cleanedScene[field] = cleaned;
|
|
914
978
|
}
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
979
|
+
const maximumNarrationWords = sceneWindows[sceneIndex].maxNarrationWords;
|
|
980
|
+
const narrationWords = cleanedScene.narration.split(/\s+/);
|
|
981
|
+
if (narrationWords.length > maximumNarrationWords) {
|
|
982
|
+
cleanedScene.narration = narrationWords.slice(0, maximumNarrationWords).join(' ');
|
|
983
|
+
}
|
|
921
984
|
return cleanedScene;
|
|
922
985
|
});
|
|
923
986
|
const finalCopy = `${scenes.at(-1).body} ${scenes.at(-1).narration}`.toLowerCase();
|
|
@@ -1072,7 +1135,7 @@ async function generateOrLoadPromoCopy(
|
|
|
1072
1135
|
const framePaths = extractAnalysisFrames(ffmpegCommand, analysisInputPath, analysisCrop);
|
|
1073
1136
|
let previousError = '';
|
|
1074
1137
|
let generated;
|
|
1075
|
-
for (let attempt = 1; attempt <=
|
|
1138
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
1076
1139
|
const prompt = buildPromoCopyPrompt(appContext, previousError);
|
|
1077
1140
|
try {
|
|
1078
1141
|
generated = provider === 'openai'
|
|
@@ -1082,7 +1145,7 @@ async function generateOrLoadPromoCopy(
|
|
|
1082
1145
|
break;
|
|
1083
1146
|
} catch (error) {
|
|
1084
1147
|
previousError = error.message;
|
|
1085
|
-
if (attempt ===
|
|
1148
|
+
if (attempt === 3 || /unavailable|not configured|not signed in/i.test(previousError)) throw error;
|
|
1086
1149
|
console.warn(`AI copy attempt ${attempt} was invalid; requesting a corrected result.`);
|
|
1087
1150
|
}
|
|
1088
1151
|
}
|
|
@@ -1639,8 +1702,9 @@ function getSingleOutputPath(inputPath, targetWidth, targetHeight) {
|
|
|
1639
1702
|
);
|
|
1640
1703
|
}
|
|
1641
1704
|
|
|
1642
|
-
async function main() {
|
|
1643
|
-
const options = parseArguments();
|
|
1705
|
+
async function main() {
|
|
1706
|
+
const options = parseArguments();
|
|
1707
|
+
await ensureAppStoreDownloadUrl();
|
|
1644
1708
|
if (options.listVoices) {
|
|
1645
1709
|
printVoiceCatalog();
|
|
1646
1710
|
return;
|
|
@@ -5,8 +5,10 @@ const { spawn, spawnSync } = require('node:child_process');
|
|
|
5
5
|
|
|
6
6
|
const screenshotDirectory = path.join(__dirname, 'Auto-Screenshot', 'Store-image-Screenshot');
|
|
7
7
|
const inFrameDirectory = path.join(__dirname, 'Auto-Screenshot', 'Store-image-Frame');
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const aiAssetsDirectory = path.join(__dirname, 'AI-Assets');
|
|
9
|
+
const amazonAssetsDirectory = path.join(aiAssetsDirectory, 'Amazon-Assets');
|
|
10
|
+
const samsungAssetsDirectory = path.join(aiAssetsDirectory, 'Samsung-Assets');
|
|
11
|
+
const playstoreAssetsDirectory = path.join(aiAssetsDirectory, 'Playstore-Assets');
|
|
10
12
|
const capacitorConfigPath = path.join(__dirname, 'capacitor.config.json');
|
|
11
13
|
const amazonIconPath = path.join(__dirname, 'resources', 'icon-only.png');
|
|
12
14
|
const temporaryDirectory = path.join(__dirname, 'agent-temp');
|
|
@@ -20,6 +22,9 @@ const samsungAssetDefinitions = [
|
|
|
20
22
|
{ fileName: 'edge-screen-160x2560.png', type: 'edge-screen', width: 160, height: 2560 },
|
|
21
23
|
{ fileName: 'edge-screen-single-550x2560.png', type: 'edge-screen-single-plus', width: 550, height: 2560 },
|
|
22
24
|
];
|
|
25
|
+
const playstoreAssetDefinitions = [
|
|
26
|
+
{ fileName: 'feature-graphic-1024x500.png', width: 1024, height: 500 },
|
|
27
|
+
];
|
|
23
28
|
const browserExecutablePaths = [
|
|
24
29
|
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
25
30
|
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
@@ -441,7 +446,7 @@ function printHelp() {
|
|
|
441
446
|
console.log(' node take-screen-image.js --dry-run');
|
|
442
447
|
console.log(' node take-screen-image.js --list');
|
|
443
448
|
console.log('');
|
|
444
|
-
console.log('A normal capture automatically creates any missing Amazon and
|
|
449
|
+
console.log('A normal capture automatically creates any missing Amazon, Samsung, and Play Store assets.');
|
|
445
450
|
}
|
|
446
451
|
|
|
447
452
|
function selectProfiles(input) {
|
|
@@ -1522,6 +1527,81 @@ function createAmazonAssetHtml(asset, appName, iconBuffer, storeCopy) {
|
|
|
1522
1527
|
</html>`;
|
|
1523
1528
|
}
|
|
1524
1529
|
|
|
1530
|
+
function createPlaystoreFeatureHtml(appName, iconBuffer, storeCopy) {
|
|
1531
|
+
const iconDataUrl = `data:image/png;base64,${iconBuffer.toString('base64')}`;
|
|
1532
|
+
const safeAppName = escapeHtml(appName);
|
|
1533
|
+
const safeCallout = escapeHtml(storeCopy.amazonCallout);
|
|
1534
|
+
const featureMarkup = storeCopy.featureLabels.slice(0, 5)
|
|
1535
|
+
.map((label, index) => `<span><i>0${index + 1}</i>${escapeHtml(label)}</span>`)
|
|
1536
|
+
.join('');
|
|
1537
|
+
|
|
1538
|
+
return `<!doctype html>
|
|
1539
|
+
<html>
|
|
1540
|
+
<head>
|
|
1541
|
+
<meta charset="utf-8" />
|
|
1542
|
+
<style>
|
|
1543
|
+
* { box-sizing: border-box; }
|
|
1544
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
|
|
1545
|
+
body {
|
|
1546
|
+
display: flex;
|
|
1547
|
+
align-items: center;
|
|
1548
|
+
padding: 42px 64px;
|
|
1549
|
+
color: #f7fbff;
|
|
1550
|
+
background: radial-gradient(circle at 84% 18%, rgba(53,212,235,0.3), transparent 31%), linear-gradient(120deg, #111729, #231039 58%, #071522);
|
|
1551
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
1552
|
+
}
|
|
1553
|
+
main { display: flex; width: 100%; height: 100%; align-items: center; justify-content: space-between; gap: 48px; }
|
|
1554
|
+
.copy { max-width: 570px; }
|
|
1555
|
+
img { width: 128px; height: 128px; border-radius: 30px; object-fit: contain; }
|
|
1556
|
+
small { display: block; margin-top: 24px; color: #35d4eb; font-size: 18px; font-weight: 700; letter-spacing: 4px; text-transform: uppercase; }
|
|
1557
|
+
h1 { margin: 12px 0 18px; font-size: 64px; line-height: 0.98; }
|
|
1558
|
+
p { margin: 0; color: #b8c5d4; font-size: 23px; line-height: 1.35; }
|
|
1559
|
+
.features { display: grid; width: 340px; gap: 12px; }
|
|
1560
|
+
.features span { display: flex; align-items: center; gap: 16px; padding: 17px 20px; border: 1px solid rgba(255,255,255,0.16); border-radius: 15px; background: rgba(8,16,28,0.6); font-size: 18px; font-weight: 700; }
|
|
1561
|
+
.features i { color: #c9ff41; font-size: 13px; font-style: normal; }
|
|
1562
|
+
</style>
|
|
1563
|
+
</head>
|
|
1564
|
+
<body><main><section class="copy"><img src="${iconDataUrl}" /><small>Explore ${safeAppName}</small><h1>${safeAppName}</h1><p>${safeCallout}</p></section><section class="features">${featureMarkup}</section></main></body>
|
|
1565
|
+
</html>`;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
async function createPlaystoreAssets(client, skipExisting = false) {
|
|
1569
|
+
if (!fs.existsSync(amazonIconPath)) {
|
|
1570
|
+
throw new Error(`Play Store asset source icon was not found: ${amazonIconPath}`);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
const config = JSON.parse(fs.readFileSync(capacitorConfigPath, 'utf8'));
|
|
1574
|
+
const appName = config.appName || 'App';
|
|
1575
|
+
const storeCopy = getStoreAssetCopy(config);
|
|
1576
|
+
const iconBuffer = fs.readFileSync(amazonIconPath);
|
|
1577
|
+
fs.mkdirSync(playstoreAssetsDirectory, { recursive: true });
|
|
1578
|
+
|
|
1579
|
+
for (const asset of playstoreAssetDefinitions) {
|
|
1580
|
+
const outputPath = path.join(playstoreAssetsDirectory, asset.fileName);
|
|
1581
|
+
if (skipExisting && fs.existsSync(outputPath)) continue;
|
|
1582
|
+
|
|
1583
|
+
await client.send('Emulation.setDeviceMetricsOverride', {
|
|
1584
|
+
width: asset.width, height: asset.height, deviceScaleFactor: 1, mobile: false,
|
|
1585
|
+
screenWidth: asset.width, screenHeight: asset.height, positionX: 0, positionY: 0,
|
|
1586
|
+
});
|
|
1587
|
+
const frameTree = await client.send('Page.getFrameTree');
|
|
1588
|
+
await client.send('Page.setDocumentContent', {
|
|
1589
|
+
frameId: frameTree.frameTree.frame.id,
|
|
1590
|
+
html: createPlaystoreFeatureHtml(appName, iconBuffer, storeCopy),
|
|
1591
|
+
});
|
|
1592
|
+
await client.send('Runtime.evaluate', {
|
|
1593
|
+
expression: `Promise.all([...document.images].map((image) => image.complete ? Promise.resolve() : new Promise((resolve) => image.addEventListener('load', resolve, { once: true })))).then(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))`,
|
|
1594
|
+
awaitPromise: true,
|
|
1595
|
+
returnByValue: true,
|
|
1596
|
+
});
|
|
1597
|
+
const screenshot = await client.send('Page.captureScreenshot', {
|
|
1598
|
+
format: 'png', fromSurface: true, captureBeyondViewport: false,
|
|
1599
|
+
});
|
|
1600
|
+
fs.writeFileSync(outputPath, Buffer.from(screenshot.data, 'base64'));
|
|
1601
|
+
console.log(`Saved Play Store feature ${asset.width}x${asset.height}: ${outputPath}`);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1525
1605
|
async function createAmazonAssets(client, skipExisting = false) {
|
|
1526
1606
|
if (!fs.existsSync(amazonIconPath)) {
|
|
1527
1607
|
throw new Error(`Amazon asset source icon was not found: ${amazonIconPath}`);
|
|
@@ -1754,9 +1834,12 @@ async function createMissingStoreAssets() {
|
|
|
1754
1834
|
const hasMissingSamsungAssets = samsungAssetDefinitions.some((asset) => (
|
|
1755
1835
|
!fs.existsSync(path.join(samsungAssetsDirectory, asset.fileName))
|
|
1756
1836
|
));
|
|
1837
|
+
const hasMissingPlaystoreAssets = playstoreAssetDefinitions.some((asset) => (
|
|
1838
|
+
!fs.existsSync(path.join(playstoreAssetsDirectory, asset.fileName))
|
|
1839
|
+
));
|
|
1757
1840
|
|
|
1758
|
-
if (!hasMissingAmazonAssets && !hasMissingSamsungAssets) {
|
|
1759
|
-
console.log('Amazon and
|
|
1841
|
+
if (!hasMissingAmazonAssets && !hasMissingSamsungAssets && !hasMissingPlaystoreAssets) {
|
|
1842
|
+
console.log('Amazon, Samsung, and Play Store assets already exist.');
|
|
1760
1843
|
return;
|
|
1761
1844
|
}
|
|
1762
1845
|
|
|
@@ -1774,12 +1857,15 @@ async function createMissingStoreAssets() {
|
|
|
1774
1857
|
if (hasMissingSamsungAssets) {
|
|
1775
1858
|
await createSamsungAssets(client, true);
|
|
1776
1859
|
}
|
|
1860
|
+
if (hasMissingPlaystoreAssets) {
|
|
1861
|
+
await createPlaystoreAssets(client, true);
|
|
1862
|
+
}
|
|
1777
1863
|
} finally {
|
|
1778
1864
|
if (client) client.close();
|
|
1779
1865
|
if (browser) await closeReplayBrowser(browser);
|
|
1780
1866
|
}
|
|
1781
1867
|
|
|
1782
|
-
console.log('Missing Amazon and
|
|
1868
|
+
console.log('Missing Amazon, Samsung, and Play Store assets created.');
|
|
1783
1869
|
}
|
|
1784
1870
|
|
|
1785
1871
|
async function captureFramedScreenshot(
|