diff --git a/build/build-portable.cjs b/build/build-portable.cjs index 7e21b4cf..f54ebd8d 100644 --- a/build/build-portable.cjs +++ b/build/build-portable.cjs @@ -159,7 +159,10 @@ function portableRequiredPaths(directory) { } function assertPortableOutput(directory) { - const requiredPaths = portableRequiredPaths(directory) + const requiredPaths = [ + ...portableRequiredPaths(directory), + join(directory, 'resources', 'runtimes', 'dsh', 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js') + ] const missing = requiredPaths.filter( (filePath) => !statSync(filePath, { throwIfNoEntry: false })?.isFile() ) diff --git a/docs/pr-articles/2026-09-26-unreleased/continue.png b/docs/pr-articles/2026-09-26-unreleased/continue.png new file mode 100644 index 00000000..d24635c9 Binary files /dev/null and b/docs/pr-articles/2026-09-26-unreleased/continue.png differ diff --git a/docs/pr-articles/2026-09-26-unreleased/opencode.png b/docs/pr-articles/2026-09-26-unreleased/opencode.png new file mode 100644 index 00000000..c59d040f Binary files /dev/null and b/docs/pr-articles/2026-09-26-unreleased/opencode.png differ diff --git a/package.json b/package.json index f4e143cc..d2dad8ae 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,8 @@ ], "extraResources": [ { - "from": ".runtime-resources/dsh-${arch}", - "to": "runtimes/dsh" + "from": ".runtime-resources/dsh-${arch}/node_modules", + "to": "runtimes/dsh/node_modules" }, { "from": "resources/skills", diff --git a/tests/build-portable.test.ts b/tests/build-portable.test.ts index e9ebe23a..fcc9c25b 100644 --- a/tests/build-portable.test.ts +++ b/tests/build-portable.test.ts @@ -84,7 +84,7 @@ describe('Portable package resources', () => { ).toBe(false) }) - it('validates the metadata-only portable output', () => { + it('validates portable resources including the DS Web entry', () => { const requiredPaths = portableBuilder.portableRequiredPaths(directory) const missingPath = requiredPaths.at(-1) @@ -98,6 +98,10 @@ describe('Portable package resources', () => { ).toThrow(missingPath) mkdirSync(dirname(missingPath!), { recursive: true }) writeFileSync(missingPath!, 'required') + const dshEntry = join(directory, 'resources', 'runtimes', 'dsh', 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js') + expect(() => portableBuilder.assertPortableOutput(directory)).toThrow(dshEntry) + mkdirSync(dirname(dshEntry), { recursive: true }) + writeFileSync(dshEntry, 'required') expect(() => portableBuilder.assertPortableOutput(directory) ).not.toThrow() diff --git a/tests/native-dsh-packaging.test.ts b/tests/native-dsh-packaging.test.ts new file mode 100644 index 00000000..20a5f401 --- /dev/null +++ b/tests/native-dsh-packaging.test.ts @@ -0,0 +1,35 @@ +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { expect, it } from 'vitest' + +const require = createRequire(import.meta.url) +const { getFileMatchers, copyFiles } = require('app-builder-lib/out/fileMatcher') + +it('copies DS dependencies and nested assets through the actual builder matcher', async () => { + const root = mkdtempSync(join(tmpdir(), 'goodbuddy-dsh-packaging-')) + const config = JSON.parse(readFileSync(resolve('package.json'), 'utf8')) + const resource = config.build.extraResources.find((entry: { to: string }) => entry.to.startsWith('runtimes/dsh')) + const files = ['@deepseek-ai/dsh/lib/bin.js', '@deepseek-ai/dsh/node_modules/example/index.js', '@deepseek-ai/dsh-web/dist/index.html'] + try { + for (const file of files) { + const source = join(root, '.runtime-resources/dsh-x64/node_modules', file) + mkdirSync(dirname(source), { recursive: true }) + writeFileSync(source, file) + } + const destination = join(root, 'output/resources') + const matchers = getFileMatchers({ extraResources: [resource] }, 'extraResources', destination, { + defaultSrc: root, + globalOutDir: join(root, 'output'), + customBuildOptions: {}, + macroExpander: (value: string) => value.replaceAll('${arch}', 'x64') + }) + await copyFiles(matchers) + for (const file of files) { + expect(readFileSync(join(destination, 'runtimes/dsh/node_modules', file), 'utf8')).toBe(file) + } + } finally { + rmSync(root, { recursive: true, force: true }) + } +})