Skip to content

Commit 4d08ce8

Browse files
authored
fix: add Override entries to VSIX [Content_Types].xml for extension-less pac CLI files (#1367)
1 parent 439520f commit 4d08ce8

5 files changed

Lines changed: 81 additions & 3 deletions

File tree

gulp/lib/nugetInstall.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ export default async function nugetInstall(pkg, feeds) {
6666
targetDir,
6767
...pkg.chmod.split(/[\\/]/g)
6868
);
69-
chmod(exePath, 0o711);
69+
chmod(exePath, 0o711).then(resolve).catch(reject);
70+
} else {
71+
resolve();
7072
}
71-
resolve();
7273
})
7374
.on("error", reject);
7475
});

gulp/pack.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import { createCommandRunner } from "@microsoft/powerplatform-cli-wrapper";
66
import find from "find";
77
import path from "path";
88
import { rm } from "fs/promises";
9+
import { info } from "fancy-log";
910

1011
import { createRequire } from "node:module";
1112
const require = createRequire(import.meta.url);
1213
const tar = require("tar");
14+
const AdmZip = require("adm-zip");
1315

1416
const outDir = 'out';
1517
const stagingDir = `${outDir}/staging`;
@@ -266,4 +268,73 @@ async function generateAllStages(manifest, taskVersion, manifestVersion) {
266268
outputPath: packagesDir,
267269
});
268270
}
271+
272+
// Fix [Content_Types].xml in all generated VSIX files to add Override entries for
273+
// extension-less files inside the pac CLI directories.
274+
//
275+
// Background: VSIX files are OPC (Open Packaging Convention) containers. Every part
276+
// (file) inside must be covered by [Content_Types].xml — either via a <Default> entry
277+
// keyed on file extension, or via an <Override> entry keyed on the full part path.
278+
//
279+
// 'tfx extension create' only generates <Default> entries keyed on extensions, so any
280+
// extension-less file gets no entry. When the VSIX is processed by OPC-aware tools
281+
// (e.g. the AzDO Marketplace ingestion pipeline), any part not registered in
282+
// [Content_Types].xml is silently dropped from the output package.
283+
//
284+
// Known extension-less files in bin/pac* that must be preserved:
285+
// bin/pac_linux/tools/pac — Linux PAC CLI executable
286+
// bin/pac*/tools/.playwright/.../xdg-open — used by Playwright for browser auth flows
287+
// bin/pac*/tools/.playwright/.../LICENSE|NOTICE — license text (harmless but included for completeness)
288+
//
289+
// Note: _rels/.rels files are already excluded during copyDependencies() so they never
290+
// reach the VSIX and do not need to be handled here.
291+
async function fixVsixContentTypes() {
292+
// Match any extension-less file inside the pac or pac_linux tool directories.
293+
const PAC_DIR_PATTERN = /^tasks\/tool-installer\/tool-installer-v2\/bin\/pac[^/]*\//;
294+
295+
const vsixFiles = await findFiles(/\.vsix$/, packagesDir);
296+
for (const vsixPath of vsixFiles) {
297+
const zip = new AdmZip(vsixPath);
298+
const ctEntry = zip.getEntry('[Content_Types].xml');
299+
if (!ctEntry) {
300+
throw new Error(`[Content_Types].xml not found in ${vsixPath}`);
301+
}
302+
303+
const ctXml = ctEntry.getData().toString('utf8');
304+
305+
// Collect part paths already covered by <Override> entries (strip leading '/').
306+
const overridePaths = new Set(
307+
[...ctXml.matchAll(/PartName="([^"]+)"/g)].map(m => m[1].replace(/^\//, ''))
308+
);
309+
310+
// Find all extension-less files inside the pac directories that are missing an Override.
311+
const newOverrides = [];
312+
for (const entry of zip.getEntries()) {
313+
if (entry.isDirectory) continue;
314+
const entryName = entry.entryName.replace(/\\/g, '/');
315+
const hasNoExtension = path.extname(entryName) === '';
316+
if (PAC_DIR_PATTERN.test(entryName) && hasNoExtension && !overridePaths.has(entryName)) {
317+
newOverrides.push(
318+
` <Override ContentType="application/octet-stream" PartName="/${entryName}"/>`
319+
);
320+
}
321+
}
322+
323+
if (newOverrides.length > 0) {
324+
const modified = ctXml.replace(
325+
'</Types>',
326+
newOverrides.join('\n') + '\n</Types>'
327+
);
328+
zip.updateFile('[Content_Types].xml', Buffer.from(modified, 'utf8'));
329+
zip.writeZip(vsixPath);
330+
info(
331+
`Fixed [Content_Types].xml in ${path.basename(vsixPath)}: ` +
332+
`added ${newOverrides.length} Override entr${newOverrides.length === 1 ? 'y' : 'ies'} ` +
333+
`(${newOverrides.map(o => /PartName="\/([^"]+)"/.exec(o)?.[1]).join(', ')})`
334+
);
335+
}
336+
}
337+
}
338+
339+
await fixVsixContentTypes();
269340
}

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@types/webpack": "^5.28.5",
4242
"@typescript-eslint/eslint-plugin": "^5.62.0",
4343
"@typescript-eslint/parser": "^5.62.0",
44+
"adm-zip": "^0.5.16",
4445
"chai": "^4.5.0",
4546
"dotenv": "^16.4.5",
4647
"eslint": "^8.50.0",

src/host/CliLocator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ export async function findPacCLIPath(): Promise<{ pacRootPath: string, pacPath:
1717
break;
1818
case 'linux':
1919
pacPath = path.resolve(pacRootPath, 'pac_linux', 'tools', 'pac');
20-
await chmod(pacPath, 0o711);
2120
break;
2221
default:
2322
throw new Error(`Unsupported OS for tool-installer: ${process.platform}`);
2423
}
2524
if (!await pathExists(pacPath)) {
2625
throw new Error(`Cannot find required pac CLI executable under: ${pacPath}`);
2726
}
27+
// chmod must come after the pathExists check so that a missing-file error is reported
28+
// clearly rather than surfacing as a confusing ENOENT from chmod itself.
29+
if (process.platform === 'linux') {
30+
await chmod(pacPath, 0o711);
31+
}
2832

2933
pacPath = pacPath.replace(/(\/|\\)(pac.exe|pac)$/, '');
3034

0 commit comments

Comments
 (0)