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
26 changes: 25 additions & 1 deletion packages/targets/desktop-linux/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { contractTestTarget, fakeBuildContext, smokeTest } from '@profullstack/sh1pt-core/testing';
import { contractTestTarget, fakeBuildContext, fakeShipContext, smokeTest } from '@profullstack/sh1pt-core/testing';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
Expand Down Expand Up @@ -48,4 +48,28 @@ describe('Linux desktop target planning', () => {
direct: { host: 'github-releases', project: 'acme/app' },
});
});

it('rejects invalid app identifiers while building', async () => {
const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-linux-'));
tempDirs.push(outDir);

await expect(adapter.build(fakeBuildContext({
outDir,
version: '1.2.3',
channel: 'stable',
}) as any, {
...sampleConfig,
appId: '../acme',
})).rejects.toThrow('desktop-linux appId must be a valid reverse-DNS identifier');
});

it('rejects invalid app identifiers while shipping', async () => {
await expect(adapter.ship(fakeShipContext({
version: '1.2.3',
dryRun: false,
}) as any, {
...sampleConfig,
appId: 'com.acme/app',
})).rejects.toThrow('desktop-linux appId must be a valid reverse-DNS identifier');
});
});
17 changes: 15 additions & 2 deletions packages/targets/desktop-linux/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,30 @@ interface Config {
direct?: { host: 'github-releases' | 'cdn'; project?: string };
}

const APP_ID_PATTERN = /^[A-Za-z][A-Za-z0-9-]*(\.[A-Za-z][A-Za-z0-9-]*)+$/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The regex character class [A-Za-z0-9-] excludes underscores. Flatpak application IDs follow the D-Bus naming convention, which explicitly permits underscores in each segment (e.g. org.some_project.App). Without underscore support, valid Flatpak IDs will be wrongly rejected by this validator.

Suggested change
const APP_ID_PATTERN = /^[A-Za-z][A-Za-z0-9-]*(\.[A-Za-z][A-Za-z0-9-]*)+$/;
const APP_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*(\.[A-Za-z][A-Za-z0-9_-]*)+$/;


function requireAppId(config: Config): string {
const appId = config.appId?.trim();
if (!appId) throw new Error('desktop-linux requires appId');
if (!APP_ID_PATTERN.test(appId)) {
throw new Error('desktop-linux appId must be a valid reverse-DNS identifier');
}
return appId;
}

export default defineTarget<Config>({
id: 'desktop-linux',
kind: 'desktop',
label: 'Linux (AppImage / Snap / Flatpak / deb / rpm)',
async build(ctx, config) {
const appId = requireAppId(config);
const arches = config.architectures ?? ['x64', 'arm64'];
ctx.log(`build ${config.formats.join(',')} · arches=${arches.join(',')}`);
const artifactDir = join(ctx.outDir, 'linux');
const planPath = join(artifactDir, 'linux-package-plan.json');
await mkdir(artifactDir, { recursive: true });
await writeFile(planPath, `${JSON.stringify({
appId: config.appId,
appId,
version: ctx.version,
channel: ctx.channel,
formats: config.formats,
Expand All @@ -41,6 +53,7 @@ export default defineTarget<Config>({
return { artifact: planPath };
},
async ship(ctx, config) {
const appId = requireAppId(config);
const channels = config.formats
.map((f) => {
if (f === 'snap') return `Snapcraft:${config.snap?.channel ?? 'stable'}`;
Expand All @@ -57,7 +70,7 @@ export default defineTarget<Config>({
// - flatpak: open PR against flathub repo (like pkg-homebrew pattern)
// - appimage: upload to GitHub release or CDN + refresh update.json
// - deb/rpm: aptly / createrepo + sign + push to configured repo
return { id: `${config.appId}@${ctx.version}` };
return { id: `${appId}@${ctx.version}` };
},
async status(id) {
return { state: 'live', version: id };
Expand Down
Loading