Skip to content

Commit 10e08ed

Browse files
thinksyncsthinksyncs
andauthored
Republish extension as universal (#8)
* Republish extension as universal * Fix cross-platform release tests --------- Co-authored-by: thinksyncs <develop@toppymicros.com>
1 parent 8e0bd1d commit 10e08ed

7 files changed

Lines changed: 197 additions & 27 deletions

File tree

.github/workflows/daily-release.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,12 @@ jobs:
223223
env:
224224
VSCE_PAT: ${{ secrets.VSCE_PAT }}
225225
run: npm run publish:extension -- --packagePath workspace-guard.vsix --pre-release
226+
227+
- name: Verify Marketplace prerelease
228+
run: |
229+
npm run verify:marketplace-release -- \
230+
--publisher ToppyMicroServices \
231+
--name workspace-guard \
232+
--version "$DAILY_VERSION" \
233+
--mode universal
234+

.github/workflows/release.yml

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,14 @@ jobs:
5353
VSCE_PAT: ${{ secrets.VSCE_PAT }}
5454
run: npm run publish:extension -- --packagePath workspace-guard.vsix
5555

56-
- name: Verify Marketplace version
56+
- name: Verify Marketplace release
5757
run: |
5858
EXPECTED_VERSION="$(node -p "require('./package.json').version")"
59-
60-
for attempt in $(seq 1 45); do
61-
PUBLISHED_VERSION="$(npx @vscode/vsce show ToppyMicroServices.workspace-guard --json | node -e "let data=''; process.stdin.on('data', (chunk) => data += chunk); process.stdin.on('end', () => { const payload = JSON.parse(data); process.stdout.write(payload.versions?.[0]?.version ?? ''); });")"
62-
63-
if [ "$PUBLISHED_VERSION" = "$EXPECTED_VERSION" ]; then
64-
echo "Marketplace shows version $PUBLISHED_VERSION."
65-
exit 0
66-
fi
67-
68-
echo "Marketplace still shows '$PUBLISHED_VERSION'; waiting for '$EXPECTED_VERSION' (attempt $attempt/45)."
69-
sleep 20
70-
done
71-
72-
echo "Marketplace did not publish expected version $EXPECTED_VERSION." >&2
73-
exit 1
59+
npm run verify:marketplace-release -- \
60+
--publisher ToppyMicroServices \
61+
--name workspace-guard \
62+
--version "$EXPECTED_VERSION" \
63+
--mode universal
7464
7565
- name: Upload VSIX to GitHub release
7666
env:

package-lock.json

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

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
{
22
"name": "workspace-guard",
3-
"version": "0.1.11",
3+
"version": "0.1.12",
44
"private": true,
55
"displayName": "Workspace Guard",
6-
"preview": true,
76
"publisher": "ToppyMicroServices",
87
"license": "Apache-2.0",
9-
"description": "Preview: Warn before VS Code opens your home directory or other high-risk folders, and offer privacy hardening for telemetry-related settings.",
8+
"description": "Warn before VS Code opens your home directory or other high-risk folders, and offer privacy hardening for telemetry-related settings.",
109
"icon": "assets/workspace-guard-icon.png",
1110
"main": "./dist/src/vscodeExtension.js",
1211
"engines": {
@@ -223,7 +222,8 @@
223222
"test": "vitest run",
224223
"vscode:prepublish": "npm run build",
225224
"package:extension": "vsce package",
226-
"publish:extension": "vsce publish"
225+
"publish:extension": "vsce publish",
226+
"verify:marketplace-release": "node scripts/verify-marketplace-release.mjs"
227227
},
228228
"repository": {
229229
"type": "git",
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import process from 'node:process';
2+
import { execFile } from 'node:child_process';
3+
import { promisify } from 'node:util';
4+
5+
const execFileAsync = promisify(execFile);
6+
7+
function parseArgs(argv) {
8+
const args = new Map();
9+
10+
for (let index = 0; index < argv.length; index += 1) {
11+
const token = argv[index];
12+
if (!token.startsWith('--')) {
13+
continue;
14+
}
15+
16+
const key = token.slice(2);
17+
const value = argv[index + 1];
18+
if (!value || value.startsWith('--')) {
19+
throw new Error(`Missing value for --${key}`);
20+
}
21+
22+
args.set(key, value);
23+
index += 1;
24+
}
25+
26+
return args;
27+
}
28+
29+
function sleep(milliseconds) {
30+
return new Promise(resolve => setTimeout(resolve, milliseconds));
31+
}
32+
33+
async function showExtension(extensionId) {
34+
const { stdout } = await execFileAsync(
35+
'npx',
36+
['@vscode/vsce', 'show', extensionId, '--json'],
37+
{
38+
cwd: process.cwd(),
39+
maxBuffer: 1024 * 1024,
40+
env: process.env,
41+
},
42+
);
43+
44+
return JSON.parse(stdout);
45+
}
46+
47+
async function fetchPackage(url) {
48+
const response = await fetch(url, {
49+
headers: {
50+
Accept: 'application/vsix, application/octet-stream;q=0.9, */*;q=0.1',
51+
},
52+
redirect: 'follow',
53+
});
54+
55+
return response;
56+
}
57+
58+
function normalizeTargets(versions, version) {
59+
return versions
60+
.filter(candidate => candidate.version === version)
61+
.map(candidate => candidate.targetPlatform)
62+
.filter(Boolean)
63+
.sort();
64+
}
65+
66+
function compareTargets(actualTargets, expectedTargets) {
67+
const actual = new Set(actualTargets);
68+
const expected = new Set(expectedTargets);
69+
70+
const missing = expectedTargets.filter(target => !actual.has(target));
71+
const unexpected = actualTargets.filter(target => !expected.has(target));
72+
73+
return { missing, unexpected };
74+
}
75+
76+
async function main() {
77+
const args = parseArgs(process.argv.slice(2));
78+
const publisher = args.get('publisher');
79+
const extensionName = args.get('name');
80+
const version = args.get('version');
81+
const mode = args.get('mode') ?? 'universal';
82+
const targets = (args.get('targets') ?? '')
83+
.split(',')
84+
.map(target => target.trim())
85+
.filter(Boolean);
86+
const timeoutSeconds = Number(args.get('timeout-seconds') ?? '900');
87+
const pollSeconds = Number(args.get('poll-seconds') ?? '20');
88+
89+
if (!publisher || !extensionName || !version) {
90+
throw new Error('Expected --publisher, --name, and --version.');
91+
}
92+
93+
if (mode !== 'universal' && mode !== 'targeted') {
94+
throw new Error(`Unsupported mode '${mode}'. Use 'universal' or 'targeted'.`);
95+
}
96+
97+
if (mode === 'targeted' && targets.length === 0) {
98+
throw new Error('Expected --targets when --mode targeted is used.');
99+
}
100+
101+
const extensionId = `${publisher}.${extensionName}`;
102+
const deadline = Date.now() + timeoutSeconds * 1000;
103+
let lastPublishedVersion = '';
104+
105+
while (Date.now() <= deadline) {
106+
const extension = await showExtension(extensionId);
107+
const latestVersion = extension.versions?.[0]?.version ?? '';
108+
lastPublishedVersion = latestVersion;
109+
110+
if (latestVersion !== version) {
111+
console.log(
112+
`Marketplace still shows '${latestVersion || 'unknown'}'; waiting for '${version}'.`,
113+
);
114+
await sleep(pollSeconds * 1000);
115+
continue;
116+
}
117+
118+
const publishedTargets = normalizeTargets(extension.versions ?? [], version);
119+
120+
if (mode === 'universal') {
121+
if (publishedTargets.length > 0) {
122+
throw new Error(
123+
`Expected universal publish for ${extensionId}@${version}, but Marketplace advertises targets: ${publishedTargets.join(', ')}`,
124+
);
125+
}
126+
} else {
127+
const { missing, unexpected } = compareTargets(publishedTargets, targets);
128+
if (missing.length > 0 || unexpected.length > 0) {
129+
throw new Error(
130+
`Marketplace targets mismatch for ${extensionId}@${version}. Missing: ${missing.join(', ') || 'none'}. Unexpected: ${unexpected.join(', ') || 'none'}.`,
131+
);
132+
}
133+
}
134+
135+
const packageUrl = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${extensionName}/${version}/vspackage`;
136+
const packageResponse = await fetchPackage(packageUrl);
137+
const contentType = packageResponse.headers.get('content-type') ?? '';
138+
139+
if (!packageResponse.ok) {
140+
throw new Error(
141+
`Marketplace package download failed for ${extensionId}@${version}: HTTP ${packageResponse.status}`,
142+
);
143+
}
144+
145+
if (!contentType.includes('application/vsix')) {
146+
throw new Error(
147+
`Marketplace package download returned unexpected content type '${contentType}' for ${extensionId}@${version}.`,
148+
);
149+
}
150+
151+
console.log(
152+
`Marketplace release verified for ${extensionId}@${version} (${mode}, universal vspackage reachable).`,
153+
);
154+
return;
155+
}
156+
157+
throw new Error(
158+
`Marketplace did not expose expected version '${version}' for ${extensionId} before timeout. Last visible version: '${lastPublishedVersion || 'unknown'}'.`,
159+
);
160+
}
161+
162+
main().catch(error => {
163+
console.error(error instanceof Error ? error.message : String(error));
164+
process.exitCode = 1;
165+
});

tests/homeguardExtension.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ describe("activateHomeguardExtension", () => {
126126
{ folderPath: homeDir, action: "redirected" }
127127
]);
128128
expect(removedFolders).toEqual([homeDir]);
129-
expect(openedFolders).toEqual([escapeDir]);
129+
expect(openedFolders.map((entry) => path.normalize(entry))).toEqual([
130+
path.normalize(escapeDir)
131+
]);
130132
expect(infos[0]).toContain("redirected");
131133

132134
const readme = await readFile(path.join(escapeDir, "README.md"), "utf8");
@@ -232,7 +234,9 @@ describe("createHomeguardCommandHandlers", () => {
232234

233235
const target = await handlers.openEscapeFolder();
234236

235-
expect(target).toBe(path.join(homeDir, "work", "_escape"));
236-
expect(openedFolders).toEqual([path.join(homeDir, "work", "_escape")]);
237+
expect(path.normalize(target)).toBe(path.normalize(path.join(homeDir, "work", "_escape")));
238+
expect(openedFolders.map((entry) => path.normalize(entry))).toEqual([
239+
path.normalize(path.join(homeDir, "work", "_escape"))
240+
]);
237241
});
238242
});

tests/workspaceSafety.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ describe("createWorkspaceSafetyGuard", () => {
277277
expect(result.disposition).toBe("blocked");
278278
expect(result.result).toBeUndefined();
279279
expect(removedFolders).toEqual([homeDir]);
280-
expect(openedFolders).toEqual([path.join(homeDir, "work", "_escape")]);
280+
expect(openedFolders.map((entry) => path.normalize(entry))).toEqual([
281+
path.normalize(path.join(homeDir, "work", "_escape"))
282+
]);
281283
});
282284

283285
it("cancels git operations when the user declines confirmation", async () => {
@@ -297,4 +299,4 @@ describe("createWorkspaceSafetyGuard", () => {
297299
expect(result.allowed).toBe(false);
298300
expect(result.disposition).toBe("cancelled");
299301
});
300-
});
302+
});

0 commit comments

Comments
 (0)