codeplay-common 4.5.0 → 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
- const appStoreDownloadUrl = /^https:\/\/apps\.apple\.com\/app\/id\d+$/.test(configuredAppStoreUrl)
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 getCodexExecutable() {
727
- const result = spawnSync('codex', ['--version'], {
728
- encoding: 'utf8',
729
- windowsHide: true,
730
- });
731
- return !result.error && result.status === 0 ? 'codex' : '';
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 narrationWords = cleanedScene.narration.split(/\s+/).length;
916
- if (narrationWords > sceneWindows[sceneIndex].maxNarrationWords) {
917
- throw new Error(
918
- `Scene ${sceneIndex + 1} narration has ${narrationWords} words; maximum is ${sceneWindows[sceneIndex].maxNarrationWords}.`,
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 <= 2; attempt += 1) {
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 === 2 || /unavailable|not configured|not signed in/i.test(previousError)) throw error;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeplay-common",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Common build scripts and files",
5
5
  "scripts": {
6
6
  "postinstall": "node scripts/sync-files.js",