File size: 4,282 Bytes
6a2bc3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/env node
import { createRequire } from 'node:module';
import { mkdir } from 'node:fs/promises';
import { join, resolve } from 'node:path';

import { runLocalCli } from './local-cli.mjs';
import {
  defaultVsixOutputDir,
  extensionRoot,
  isMainModule,
  normalizeVsixTargets,
  vsixFileName,
} from './vsix-targets.mjs';
import { verifyVsix } from './vsix-verify.mjs';

const require = createRequire(import.meta.url);

function parseArguments(argv) {
  const targets = [];
  let outputDir = defaultVsixOutputDir;
  let dryRun = false;
  let help = false;

  for (let index = 0; index < argv.length; index += 1) {
    const argument = argv[index];
    if (argument === '--') {
      continue;
    } else if (argument === '--help' || argument === '-h') {
      help = true;
    } else if (argument === '--dry-run') {
      dryRun = true;
    } else if (argument === '--out-dir') {
      outputDir = requireOptionValue(argv, ++index, '--out-dir');
    } else if (argument === '--target') {
      targets.push(requireOptionValue(argv, ++index, '--target'));
    } else if (argument.startsWith('-')) {
      throw new Error(`Unknown option: ${argument}`);
    } else {
      targets.push(argument);
    }
  }

  return {
    targets: normalizeVsixTargets(targets),
    outputDir: resolve(outputDir),
    dryRun,
    help,
  };
}

function requireOptionValue(argv, index, option) {
  const value = argv[index];
  if (value !== undefined && !value.startsWith('-')) return value;
  throw new Error(`${option} requires a value.`);
}

function usage() {
  return [
    'Usage: node scripts/vsix-package.mjs [targets...] [--out-dir <directory>]',
    '       node scripts/vsix-package.mjs --target win32-x64 --dry-run',
    '',
    'With no targets, all six supported VSIX targets are built and audited.',
    'This command never publishes an extension.',
  ].join('\n');
}

async function main() {
  const options = parseArguments(process.argv.slice(2));
  if (options.help) {
    console.log(usage());
    return;
  }

  await mkdir(options.outputDir, { recursive: true });
  console.log(`VSIX targets: ${options.targets.join(', ')}`);
  console.log(`Output directory: ${options.outputDir}`);

  if (!options.dryRun) {
    console.log('Building the extension once before packaging all targets...');
    buildExtension();
  }

  const packVsix = options.dryRun ? undefined : loadVscePack();

  for (const target of options.targets) {
    const outputPath = join(options.outputDir, vsixFileName(target));
    if (options.dryRun) {
      console.log(`Would package ${target} -> ${outputPath}`);
      continue;
    }

    console.log(`\nPackaging ${target} with the workspace @vscode/vsce...`);
    await packVsix({
      cwd: extensionRoot,
      dependencies: false,
      packagePath: outputPath,
      target,
    });
    const result = await verifyVsix(outputPath, target, { sourceRoot: extensionRoot });
    console.log(
      `Verified ${target}: ${result.files} files, ${result.bytes} unpacked bytes; package/static checks passed.`,
    );
  }

  if (!options.dryRun) {
    console.log(
      '\nVSIX packaging complete. These are package-only results until each target runs in its matching extension host.',
    );
  }
}

function buildExtension() {
  runLocalCli('tsdown', 'tsdown', ['--config', 'tsdown.config.ts'], { cwd: extensionRoot });
  runLocalCli('vite', 'vite', ['build', '--config', 'webview-ui/vite.config.ts'], {
    cwd: extensionRoot,
  });
}

function loadVscePack() {
  try {
    // VSCE's public API always runs vscode:prepublish. We build once above and pin VSCE,
    // so use its package phase directly instead of rebuilding for every target.
    const module = require('@vscode/vsce/out/package.js');
    if (typeof module.pack !== 'function') {
      throw new TypeError('the installed package does not expose pack()');
    }
    return module.pack;
  } catch (error) {
    throw new Error(
      'The workspace @vscode/vsce package API is unavailable. Run pnpm install; runtime CLI downloads are disabled.',
      { cause: error },
    );
  }
}

if (isMainModule(import.meta.url)) {
  main().catch((error) => {
    console.error(`VSIX packaging failed: ${error instanceof Error ? error.message : String(error)}`);
    process.exitCode = 1;
  });
}