@playcraft/build 0.0.43 → 0.0.45

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.
@@ -148,7 +148,7 @@ export declare class PlayableScriptsAdapter {
148
148
  private copyToOutputDir;
149
149
  /**
150
150
  * 是否对 playable-scripts 使用 --zip。
151
- * Google/Facebook 等双格式渠道默认 HTML,与 CLI、Portal、GoogleAdapter 一致;仅 ZIP 的渠道仍强制 ZIP
151
+ * 双格式渠道默认 HTML;仅在请求或配置显式选择 ZIP 时传递 --zip
152
152
  */
153
153
  private isZipOutput;
154
154
  /**
@@ -258,7 +258,7 @@ export class PlayableScriptsAdapter {
258
258
  // 输出目录 - 统一使用 dist,与 PlayCraft 标准一致
259
259
  const outputDir = config.outputDir ?? 'dist';
260
260
  args.push('--out-dir', outputDir);
261
- // ZIP 输出:仅 ZIP-only 渠道、显式 config.zip、或构建请求 format=zip(如 Google / Facebook ZIP)
261
+ // ZIP 输出:显式 config.zip 或构建请求 format=zip(如 Google / Facebook / TikTok 等选 ZIP)
262
262
  const isZipFormat = this.isZipOutput();
263
263
  if (isZipFormat) {
264
264
  args.push('--zip');
@@ -631,6 +631,12 @@ export class PlayableScriptsAdapter {
631
631
  'ignore-scripts=false',
632
632
  'strict-peer-dependencies=false',
633
633
  'dangerously-allow-all-builds=true',
634
+ // pnpm >=10.10 默认启用 minimum-release-age=24h(supply-chain 防护,
635
+ // 阻止安装发布不满 24 小时的包),构建端的 worker 镜像若意外跑到 pnpm 11
636
+ // (corepack 行为差异等),此策略会让恰好今天发版的依赖(如 vue@3.5.x)报
637
+ // ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION。这里在临时 .npmrc 里强制关掉,
638
+ // install 完即 unlink,仅作用于本次 PlayCraft 构建,不影响外部环境。
639
+ 'minimum-release-age=0',
634
640
  ].join('\n');
635
641
  try {
636
642
  await fs.writeFile(npmrcPath, npmrcContent, 'utf-8');
@@ -646,19 +652,34 @@ export class PlayableScriptsAdapter {
646
652
  try {
647
653
  // 执行 pnpm install 安装 package.json 中定义的所有依赖
648
654
  // 包括 @playcraft/devkit 和 babel-loader 等构建工具
649
- // 注意:不要设置 NODE_ENV=production,否则会跳过 devDependencies
650
655
  await this.runCommandWithTimeout('pnpm', installArgs, {
651
656
  cwd: this.projectDir,
652
- timeout: 5 * 60 * 1000,
653
- env: process.env,
657
+ timeout: 10 * 60 * 1000,
658
+ env: {
659
+ ...process.env,
660
+ NODE_ENV: 'development',
661
+ },
654
662
  });
655
663
  }
656
664
  catch (error) {
665
+ const errMsg = error.message ?? String(error);
666
+ if (errMsg.startsWith('命令被中止')) {
667
+ // 清理 .npmrc 后再抛,避免临时配置残留
668
+ try {
669
+ await fs.unlink(npmrcPath);
670
+ }
671
+ catch { /* ignore */ }
672
+ throw new Error(`[PlayableScriptsAdapter] pnpm install 被中止(5 分钟 timeout)。\n` +
673
+ `install 未跑完 link-bin 阶段,node_modules/.bin 软链缺失,无法继续构建。\n` +
674
+ `常见原因:依赖 1000+ 包冷启动、mozjpeg/sharp 等 native postinstall 编译过慢。\n` +
675
+ `建议:worker 镜像预热 pnpm store / 修复 mozjpeg 预编译产物 / 调高 install timeout。\n` +
676
+ `原始错误: ${errMsg}`);
677
+ }
657
678
  // 不立即放弃:新版 pnpm 即使 install 实际成功,也可能因 ERR_PNPM_IGNORED_BUILDS
658
679
  // 等告警退出非零;devkit 本身不需要 native build,只要 node_modules 里能定位到
659
680
  // CLI 入口,构建即可继续。先记下错误,后面 verify 再决定是否真正失败。
660
681
  installError = error;
661
- console.log(`[PlayableScriptsAdapter] ⚠️ pnpm install 退出非零,将再次校验 node_modules 实际状态: ${error.message}`);
682
+ console.log(`[PlayableScriptsAdapter] ⚠️ pnpm install 退出非零,将再次校验 node_modules 实际状态: ${errMsg}`);
662
683
  }
663
684
  // 清理临时 .npmrc
664
685
  try {
@@ -681,12 +702,28 @@ export class PlayableScriptsAdapter {
681
702
  path.join(devkitDir, 'dist', 'cli.js'),
682
703
  path.join(devkitDir, 'bin', 'playable-scripts.js'),
683
704
  ];
705
+ const dotBinPath = path.join(this.projectDir, 'node_modules', '.bin', 'playable-scripts');
706
+ let dotBinReady = false;
707
+ try {
708
+ await fs.access(dotBinPath);
709
+ dotBinReady = true;
710
+ }
711
+ catch {
712
+ // .bin 软链不存在,下面 fallback 路径也会再检一次
713
+ }
684
714
  for (const binPath of possiblePaths) {
685
715
  try {
686
716
  await fs.access(binPath);
717
+ if (!dotBinReady) {
718
+ // CLI 物理文件已到位但 .bin 软链缺失 —— install 没跑完 link-bin 阶段
719
+ console.error(`[PlayableScriptsAdapter] ❌ 找到 CLI 物理文件 (${binPath}) 但 .bin/playable-scripts 软链缺失,` +
720
+ `判定 install 未跑完 link-bin 阶段,拒绝继续构建`);
721
+ // 跳出 for 循环,让外层走 fallback / return null
722
+ break;
723
+ }
687
724
  console.log(`[PlayableScriptsAdapter] 安装后找到 playable-scripts: ${binPath}`);
688
725
  if (installError) {
689
- console.log('[PlayableScriptsAdapter] ✅ pnpm install 虽然退出非零,但 devkit 已就绪,忽略告警继续构建');
726
+ console.log('[PlayableScriptsAdapter] ✅ pnpm install 虽然退出非零,但 devkit 与 .bin 软链均已就绪,忽略告警继续构建');
690
727
  }
691
728
  return binPath;
692
729
  }
@@ -694,7 +731,9 @@ export class PlayableScriptsAdapter {
694
731
  // 继续尝试下一个
695
732
  }
696
733
  }
697
- console.error(`[PlayableScriptsAdapter] ❌ 找到 devkit 但无法定位 CLI: ${devkitDir}`);
734
+ if (dotBinReady) {
735
+ console.error(`[PlayableScriptsAdapter] ❌ 找到 devkit 但无法定位 CLI: ${devkitDir}`);
736
+ }
698
737
  }
699
738
  catch {
700
739
  // require.resolve 失败,回退到 findLocalPlayableScripts
@@ -835,7 +874,14 @@ export class PlayableScriptsAdapter {
835
874
  await this.runCommandWithTimeout('pnpm', installArgs, {
836
875
  cwd: this.projectDir,
837
876
  timeout: 3 * 60 * 1000,
838
- env: process.env,
877
+ // NODE_ENV 覆盖原因同 ensureLocalDevkit 中 pnpm install:
878
+ // worker 容器自身 ENV NODE_ENV=production,若直接透传,pnpm 会按 --prod 处理,
879
+ // 即使是 `pnpm add -D` 也存在跨版本行为差异的风险。这里强制 development 保证
880
+ // install 主路径与 add 兜底路径行为一致。
881
+ env: {
882
+ ...process.env,
883
+ NODE_ENV: 'development',
884
+ },
839
885
  });
840
886
  }
841
887
  catch (error) {
@@ -1155,25 +1201,13 @@ export class PlayableScriptsAdapter {
1155
1201
  }
1156
1202
  /**
1157
1203
  * 是否对 playable-scripts 使用 --zip。
1158
- * Google/Facebook 等双格式渠道默认 HTML,与 CLI、Portal、GoogleAdapter 一致;仅 ZIP 的渠道仍强制 ZIP
1204
+ * 双格式渠道默认 HTML;仅在请求或配置显式选择 ZIP 时传递 --zip
1159
1205
  */
1160
1206
  isZipOutput() {
1161
1207
  const config = this.config ?? {};
1162
- const channels = config.channels ?? ['google'];
1163
1208
  if (config.zip === true) {
1164
1209
  return true;
1165
1210
  }
1166
- const zipOnlyPlatforms = new Set([
1167
- 'tiktok',
1168
- 'snapchat',
1169
- 'liftoff',
1170
- 'bigo',
1171
- 'inmobi',
1172
- 'mintegral',
1173
- ]);
1174
- if (channels.some((ch) => zipOnlyPlatforms.has(ch))) {
1175
- return true;
1176
- }
1177
1211
  const fmt = this.options.format;
1178
1212
  return fmt === 'zip';
1179
1213
  }
@@ -8,7 +8,7 @@ export class BigoAdapter extends PlatformAdapter {
8
8
  return 5 * 1024 * 1024;
9
9
  }
10
10
  getDefaultFormat() {
11
- return 'zip';
11
+ return 'html';
12
12
  }
13
13
  async modifyHTML(html, assets) {
14
14
  // BIGO Ads 需要 BIGO JS-SDK
@@ -36,7 +36,7 @@ export class BigoAdapter extends PlatformAdapter {
36
36
  `;
37
37
  const sdkScript = `<script src="https://static-web.likeevideo.com/oss/material-ad/playable-ad-demo/bgy-mraid-sdk.js"></script>`;
38
38
  const fallbackScript = await this.minifyPlatformScript(bigoScriptCode, 'bigo-sdk');
39
- const comment = `<!-- BIGO: ZIP格式, config.jsonorientation, 使用BGY_MRAID.open() -->`;
39
+ const comment = `<!-- BIGO: HTML/ZIP format, config.json includes orientation when present, use BGY_MRAID.open() -->`;
40
40
  // 在 </head> 之前插入
41
41
  html = html.replace('</head>', `${sdkScript}${fallbackScript}${comment}</head>`);
42
42
  // 注入统一的 CTA 适配器
@@ -71,8 +71,6 @@ export class BigoAdapter extends PlatformAdapter {
71
71
  };
72
72
  }
73
73
  validateOptions() {
74
- if (this.options.format && this.options.format !== 'zip') {
75
- console.warn('警告: BIGO Ads 要求 ZIP 格式');
76
- }
74
+ // BIGO Ads supports both HTML and ZIP output in PlayCraft.
77
75
  }
78
76
  }
@@ -8,7 +8,7 @@ export class InMobiAdapter extends PlatformAdapter {
8
8
  return 5 * 1024 * 1024;
9
9
  }
10
10
  getDefaultFormat() {
11
- return 'zip';
11
+ return 'html';
12
12
  }
13
13
  async modifyHTML(html, assets) {
14
14
  // InMobi 需要 MRAID 支持
@@ -36,7 +36,7 @@ export class InMobiAdapter extends PlatformAdapter {
36
36
  };
37
37
  `;
38
38
  const inmobiScript = await this.minifyPlatformScript(inmobiScriptCode, 'inmobi-mraid');
39
- const comment = `<!-- InMobi: ZIP格式, 使用mraid.open() -->`;
39
+ const comment = `<!-- InMobi: HTML/ZIP format, use mraid.open() -->`;
40
40
  // 在 </head> 之前插入平台特定的 API
41
41
  html = html.replace('</head>', `${inmobiScript}${comment}</head>`);
42
42
  // 注入统一的 CTA 适配器
@@ -57,8 +57,6 @@ export class InMobiAdapter extends PlatformAdapter {
57
57
  `;
58
58
  }
59
59
  validateOptions() {
60
- if (this.options.format && this.options.format !== 'zip') {
61
- console.warn('警告: InMobi 要求 ZIP 格式');
62
- }
60
+ // InMobi supports both HTML and ZIP output in PlayCraft.
63
61
  }
64
62
  }
@@ -8,7 +8,7 @@ export class LiftoffAdapter extends PlatformAdapter {
8
8
  return 5 * 1024 * 1024;
9
9
  }
10
10
  getDefaultFormat() {
11
- return 'zip';
11
+ return 'html';
12
12
  }
13
13
  async modifyHTML(html, assets) {
14
14
  // Liftoff 支持 MRAID 或 window.open
@@ -31,7 +31,7 @@ export class LiftoffAdapter extends PlatformAdapter {
31
31
  };
32
32
  `;
33
33
  const liftoffScript = await this.minifyPlatformScript(liftoffScriptCode, 'liftoff-mraid');
34
- const comment = `<!-- Liftoff: ZIP格式, ASCII文件名, 使用mraid.open()window.open() -->`;
34
+ const comment = `<!-- Liftoff: HTML/ZIP format, ASCII filenames recommended, use mraid.open() or window.open() -->`;
35
35
  // 在 </head> 之前插入
36
36
  html = html.replace('</head>', `${liftoffScript}${comment}</head>`);
37
37
  // 注入统一的 CTA 适配器
@@ -47,8 +47,6 @@ export class LiftoffAdapter extends PlatformAdapter {
47
47
  `;
48
48
  }
49
49
  validateOptions() {
50
- if (this.options.format && this.options.format !== 'zip') {
51
- console.warn('警告: Liftoff 要求 ZIP 格式');
52
- }
50
+ // Liftoff supports both HTML and ZIP output in PlayCraft.
53
51
  }
54
52
  }
@@ -4,11 +4,11 @@ export class MintegralAdapter extends PlatformAdapter {
4
4
  return 'Mintegral';
5
5
  }
6
6
  getSizeLimit() {
7
- // Mintegral: 5MB (ZIP)
7
+ // Mintegral: 5MB
8
8
  return 5 * 1024 * 1024;
9
9
  }
10
10
  getDefaultFormat() {
11
- return 'zip';
11
+ return 'html';
12
12
  }
13
13
  async modifyHTML(html, assets) {
14
14
  // Mintegral 要求 charset 为 utf-8,确保存在
@@ -33,7 +33,7 @@ export class MintegralAdapter extends PlatformAdapter {
33
33
  window.HttpAPI = window.HttpAPI || { sendPoint: function() {} };
34
34
  `;
35
35
  const mintegralScript = await this.minifyPlatformScript(mintegralScriptCode, 'mintegral-sdk');
36
- const comment = `<!-- Mintegral: ZIP格式, 5MB限制, 使用window.install()跳转, 支持横竖屏 -->`;
36
+ const comment = `<!-- Mintegral: HTML/ZIP format, 5MB limit, use window.install(), supports both orientations -->`;
37
37
  // 在 </head> 之前插入 polyfill 和注释,如果没有 </head> 则在文件开头插入
38
38
  if (html.includes('</head>')) {
39
39
  html = html.replace('</head>', `${mintegralScript}${comment}\n</head>`);
@@ -59,8 +59,6 @@ export class MintegralAdapter extends PlatformAdapter {
59
59
  `;
60
60
  }
61
61
  validateOptions() {
62
- if (this.options.format && this.options.format !== 'zip') {
63
- console.warn('警告: Mintegral 要求 ZIP 格式,将自动使用 ZIP');
64
- }
62
+ // Mintegral supports both HTML and ZIP output in PlayCraft.
65
63
  }
66
64
  }
@@ -8,7 +8,7 @@ export class SnapchatAdapter extends PlatformAdapter {
8
8
  return 5 * 1024 * 1024;
9
9
  }
10
10
  getDefaultFormat() {
11
- return 'zip';
11
+ return 'html';
12
12
  }
13
13
  async modifyHTML(html, assets) {
14
14
  // Snapchat 需要 MRAID 2.0 和 snapchatCta 函数
@@ -8,7 +8,7 @@ export class TikTokAdapter extends PlatformAdapter {
8
8
  return 5 * 1024 * 1024;
9
9
  }
10
10
  getDefaultFormat() {
11
- return 'zip';
11
+ return 'html';
12
12
  }
13
13
  async modifyHTML(html, assets) {
14
14
  // TikTok/Pangle 需要 Pangle JS-SDK
@@ -35,7 +35,7 @@ export class TikTokAdapter extends PlatformAdapter {
35
35
  };
36
36
  `;
37
37
  const pangleScript = await this.minifyPlatformScript(pangleScriptCode, 'pangle-sdk');
38
- const comment = `<!-- TikTok/Pangle: ZIP格式, config.jsonplayable_orientation, 禁止mraid.js -->`;
38
+ const comment = `<!-- TikTok/Pangle: HTML/ZIP format, config.json includes playable_orientation when present, no mraid.js -->`;
39
39
  // 在 </head> 之前插入
40
40
  html = html.replace('</head>', `${pangleScript}${comment}</head>`);
41
41
  // 注入统一的 CTA 适配器
@@ -63,8 +63,6 @@ export class TikTokAdapter extends PlatformAdapter {
63
63
  };
64
64
  }
65
65
  validateOptions() {
66
- if (this.options.format && this.options.format !== 'zip') {
67
- console.warn('警告: TikTok/Pangle 要求 ZIP 格式');
68
- }
66
+ // TikTok/Pangle supports both HTML and ZIP output in PlayCraft.
69
67
  }
70
68
  }
@@ -67,7 +67,7 @@ export const PLATFORM_CONFIGS = {
67
67
  },
68
68
  snapchat: {
69
69
  sizeLimit: 5 * 1024 * 1024, // 5MB
70
- outputFormat: 'zip',
70
+ outputFormat: 'html',
71
71
  minifyCSS: true,
72
72
  minifyJS: true,
73
73
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -185,7 +185,7 @@ export const PLATFORM_CONFIGS = {
185
185
  },
186
186
  tiktok: {
187
187
  sizeLimit: 5 * 1024 * 1024, // 5MB
188
- outputFormat: 'zip',
188
+ outputFormat: 'html',
189
189
  minifyCSS: true,
190
190
  minifyJS: true,
191
191
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -243,7 +243,7 @@ export const PLATFORM_CONFIGS = {
243
243
  },
244
244
  liftoff: {
245
245
  sizeLimit: 5 * 1024 * 1024, // 5MB
246
- outputFormat: 'zip',
246
+ outputFormat: 'html',
247
247
  minifyCSS: true,
248
248
  minifyJS: true,
249
249
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -301,7 +301,7 @@ export const PLATFORM_CONFIGS = {
301
301
  },
302
302
  bigo: {
303
303
  sizeLimit: 5 * 1024 * 1024, // 5MB
304
- outputFormat: 'zip',
304
+ outputFormat: 'html',
305
305
  minifyCSS: true,
306
306
  minifyJS: true,
307
307
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -329,7 +329,7 @@ export const PLATFORM_CONFIGS = {
329
329
  },
330
330
  inmobi: {
331
331
  sizeLimit: 5 * 1024 * 1024, // 5MB
332
- outputFormat: 'zip',
332
+ outputFormat: 'html',
333
333
  minifyCSS: true,
334
334
  minifyJS: true,
335
335
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -416,8 +416,8 @@ export const PLATFORM_CONFIGS = {
416
416
  },
417
417
  },
418
418
  mintegral: {
419
- sizeLimit: 5 * 1024 * 1024, // 5MB (ZIP)
420
- outputFormat: 'zip',
419
+ sizeLimit: 5 * 1024 * 1024, // 5MB
420
+ outputFormat: 'html',
421
421
  minifyCSS: true,
422
422
  minifyJS: true,
423
423
  compressImages: false, // 禁用图片压缩,原图输出效果更好