Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion packages/targets/browser-edge/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,88 @@
import { smokeTest } from '@profullstack/sh1pt-core/testing';
import { fakeBuildContext, smokeTest } from '@profullstack/sh1pt-core/testing';
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const { execMock } = vi.hoisted(() => ({
execMock: vi.fn(),
}));

vi.mock('@profullstack/sh1pt-core', async () => ({
...await vi.importActual<typeof import('@profullstack/sh1pt-core')>('@profullstack/sh1pt-core'),
exec: execMock,
}));

import adapter from './index.js';

smokeTest(adapter, { idPrefix: 'browser', requireKind: true });

const tempDirs: string[] = [];

beforeEach(() => {
vi.clearAllMocks();
});

afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});

describe('browser-edge target adapter', () => {
it('writes a package plan without touching the source directory in dry-run builds', async () => {
const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-edge-out-'));
const projectDir = await mkdtemp(join(tmpdir(), 'sh1pt-edge-project-'));
tempDirs.push(outDir, projectDir);

const result = await adapter.build(fakeBuildContext({
outDir,
projectDir,
version: '1.2.3',
dryRun: true,
}) as any, {
productId: 'edge-product',
sourceDir: 'extension-dist',
});

expect(execMock).not.toHaveBeenCalled();
expect(result.artifact).toBe(join(outDir, 'edge-package.json'));
const plan = JSON.parse(await readFile(result.artifact, 'utf-8'));
expect(plan).toEqual({
provider: 'microsoft-edge-addons',
productId: 'edge-product',
version: '1.2.3',
sourceDir: join(projectDir, 'extension-dist'),
artifact: join(outDir, 'edge-product-1.2.3.zip'),
command: ['zip', '-r', join(outDir, 'edge-product-1.2.3.zip'), '.'],
cwd: join(projectDir, 'extension-dist'),
});
});

it('packages the project-relative source directory with zip for real builds', async () => {
execMock.mockResolvedValue({ exitCode: 0, stdout: '', stderr: '' });

const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-edge-out-'));
const projectDir = await mkdtemp(join(tmpdir(), 'sh1pt-edge-project-'));
const sourceDir = join(projectDir, 'dist');
tempDirs.push(outDir, projectDir);
await mkdir(sourceDir, { recursive: true });
await writeFile(join(sourceDir, 'manifest.json'), JSON.stringify({ manifest_version: 3 }), 'utf-8');

const ctx = fakeBuildContext({
outDir,
projectDir,
version: '1.2.3',
dryRun: false,
});
const result = await adapter.build(ctx as any, {
productId: 'edge-product',
});

const artifact = join(outDir, 'edge-product-1.2.3.zip');
expect(execMock).toHaveBeenCalledWith('zip', ['-r', artifact, '.'], {
cwd: sourceDir,
log: ctx.log,
throwOnNonZero: true,
});
expect(result).toEqual({ artifact });
});
});
60 changes: 48 additions & 12 deletions packages/targets/browser-edge/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,72 @@
import { defineTarget, manualSetup } from '@profullstack/sh1pt-core';
import { execSync } from 'node:child_process';
import { readFileSync, existsSync } from 'node:fs';
import { defineTarget, exec, manualSetup } from '@profullstack/sh1pt-core';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { isAbsolute, join } from 'node:path';

interface Config {
productId: string; // Edge Partner Center product ID
sourceDir?: string; // defaults to "dist/"
notes?: string; // release notes for reviewer
}

function sourceDir(ctx: { projectDir: string }, config: Config): string {
const dir = config.sourceDir ?? 'dist';
return isAbsolute(dir) ? dir : join(ctx.projectDir, dir);
}

function packageArtifact(ctx: { outDir: string; version: string }, config: Config): string {
return join(ctx.outDir, `${config.productId}-${ctx.version}.zip`);
}

function packagePlan(ctx: { projectDir: string; outDir: string; version: string }, config: Config) {
const src = sourceDir(ctx, config);
const artifact = packageArtifact(ctx, config);
return {
provider: 'microsoft-edge-addons',
productId: config.productId,
version: ctx.version,
sourceDir: src,
artifact,
command: ['zip', '-r', artifact, '.'],
cwd: src,
};
}

export default defineTarget<Config>({
id: 'browser-edge',
kind: 'browser-ext',
label: 'Microsoft Edge Add-ons',
async build(ctx, config) {
const src = config.sourceDir ?? 'dist/';
const zipPath = `${ctx.outDir}/${config.productId}-${ctx.version}.zip`;
const src = sourceDir(ctx, config);
const zipPath = packageArtifact(ctx, config);

ctx.log(`pack Edge extension from ${src} for v${ctx.version}`);

if (ctx.dryRun) {
const planPath = join(ctx.outDir, 'edge-package.json');
await mkdir(ctx.outDir, { recursive: true });
await writeFile(planPath, `${JSON.stringify(packagePlan(ctx, config), null, 2)}\n`, 'utf-8');
return { artifact: planPath };
}

// Validate manifest.json exists and is manifest_version 3
const manifestPath = `${src}/manifest.json`;
if (!existsSync(manifestPath)) {
const manifestPath = join(src, 'manifest.json');
let manifestText: string;
try {
manifestText = await readFile(manifestPath, 'utf-8');
} catch {
throw new Error(`manifest.json not found at ${manifestPath} — run a build step first`);
}
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
const manifest = JSON.parse(manifestText) as { manifest_version?: number };
if (manifest.manifest_version !== 3) {
ctx.log(`manifest_version is ${manifest.manifest_version}, Edge requires v3`, 'warn');
}

// Zip the extension directory
execSync(`mkdir -p ${JSON.stringify(ctx.outDir)}`, { stdio: 'ignore' });
execSync(`cd ${JSON.stringify(src)} && zip -r ${JSON.stringify(zipPath)} .`, { stdio: 'pipe' });
await mkdir(ctx.outDir, { recursive: true });
await exec('zip', ['-r', zipPath, '.'], {
cwd: src,
log: ctx.log,
throwOnNonZero: true,
});

ctx.log(`created ${zipPath}`);
return { artifact: zipPath };
Expand Down Expand Up @@ -77,7 +113,7 @@ export default defineTarget<Config>({
// Step 2: Upload the package (zip) as a draft submission
ctx.log('uploading package...');
const uploadUrl = `https://api.addons.microsoftedge.microsoft.com/v1/products/${config.productId}/submissions/draft/package`;
const zipBuf = readFileSync(ctx.artifact);
const zipBuf = await readFile(ctx.artifact);

const uploadRes = await fetch(uploadUrl, {
method: 'PUT',
Expand Down
Loading