Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"package:linux-deb-arm64": "electron-builder --config electron-builder.config.mjs --linux deb --arm64 --publish never",
"typecheck": "tsc -p tsconfig.preload.json --noEmit && tsc -p tsconfig.main.json --noEmit && tsc -p tsconfig.renderer.json --noEmit && tsc -p tsconfig.storybook.json --noEmit",
"typecheck:stories": "tsc -p tsconfig.storybook.json --noEmit",
"test:dist": "node --test \"dist/main/**/*.test.js\" scripts/dev-app-runtime.test.mjs",
"test:dist": "node --test \"dist/main/**/*.test.js\" scripts/dev-app-runtime.test.mjs scripts/vite-workspace-packages.test.mjs",
"e2e": "npm run build:with-deps && playwright test --config e2e/playwright.config.ts",
"build:with-deps": "npm run build:workspace-deps && npm run build",
"smoke:real-window": "npm run build:with-deps && node ../../scripts/desktop-real-window-smoke.mjs",
Expand Down
10 changes: 7 additions & 3 deletions apps/desktop/scripts/check-renderer-architecture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2125,8 +2125,9 @@ function validateViteEntryContract(desktopRoot, violations) {
};
const reactCall = pluginCall(0, 'react', 0);
const dependencyPatchesCall = pluginCall(1, 'dependencyPatchesCachePlugin', 1);
const bundledPackagesCall = pluginCall(2, 'bundledNpmPackagesPlugin', 0);
const rendererContractCall = pluginCall(3, 'rendererEntryContractPlugin', 1);
const workspacePackagesCall = pluginCall(2, 'workspacePackagesPlugin', 1);
const bundledPackagesCall = pluginCall(3, 'bundledNpmPackagesPlugin', 0);
const rendererContractCall = pluginCall(4, 'rendererEntryContractPlugin', 1);
const rendererContractRoot = unwrapExpression(rendererContractCall?.arguments[0]);
const hasPinnedRendererContractRoot =
rendererContractRoot?.type === 'CallExpression' &&
Expand All @@ -2139,17 +2140,20 @@ function validateViteEntryContract(desktopRoot, violations) {
hasNamedImport(program, 'node:path', 'resolve') &&
hasDefaultImport(program, '@vitejs/plugin-react', 'react') &&
hasNamedImport(program, './vite-dependency-patches.js', 'dependencyPatchesCachePlugin') &&
hasNamedImport(program, './vite-workspace-packages.js', 'workspacePackagesPlugin') &&
hasNamedImport(program, './vite-bundled-packages.js', 'bundledNpmPackagesPlugin') &&
hasNamedImport(
program,
'./scripts/vite-renderer-entry-contract.js',
'rendererEntryContractPlugin',
);
const hasPinnedPlugins =
pluginElements.length === 4 &&
pluginElements.length === 5 &&
Boolean(reactCall) &&
Boolean(dependencyPatchesCall) &&
isIdentifier(dependencyPatchesCall.arguments[0], 'REPO_ROOT') &&
Boolean(workspacePackagesCall) &&
isIdentifier(workspacePackagesCall.arguments[0], 'REPO_ROOT') &&
Boolean(bundledPackagesCall) &&
Boolean(rendererContractCall) &&
hasPinnedRendererContractRoot;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/scripts/check-renderer-architecture.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,14 @@ function rendererEntryContractFiles(overrides = {}) {
import { rendererEntryContractPlugin } from './scripts/vite-renderer-entry-contract.js';
import { bundledNpmPackagesPlugin } from './vite-bundled-packages.js';
import { dependencyPatchesCachePlugin } from './vite-dependency-patches.js';
import { workspacePackagesPlugin } from './vite-workspace-packages.js';
const REPO_ROOT = '/fixture';
export default defineConfig({
root: 'src/renderer',
plugins: [
react(),
dependencyPatchesCachePlugin(REPO_ROOT),
workspacePackagesPlugin(REPO_ROOT),
bundledNpmPackagesPlugin(),
rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer')),
],
Expand Down
81 changes: 81 additions & 0 deletions apps/desktop/scripts/vite-workspace-packages.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import assert from 'node:assert/strict';
import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { setTimeout } from 'node:timers/promises';
import { test } from 'node:test';
import { createServer } from 'vite';
import { workspacePackagesPlugin } from '../vite-workspace-packages.ts';

test('renderer loads a newly exported workspace module after its manifest changes', async (t) => {
const repoRoot = await realpath(await mkdtemp(join(tmpdir(), 'maka-workspace-exports-')));
let server;
t.after(async () => {
await server?.close();
await rm(repoRoot, { recursive: true, force: true });
});
const root = join(repoRoot, 'apps/desktop/src/renderer');
const core = join(repoRoot, 'packages/core');
await mkdir(root, { recursive: true });
await mkdir(join(core, 'dist'), { recursive: true });
await mkdir(join(repoRoot, 'node_modules/@maka'), { recursive: true });
await symlink(core, join(repoRoot, 'node_modules/@maka/core'), 'junction');
await writeFile(join(repoRoot, 'package.json'), JSON.stringify({ workspaces: ['packages/core'] }));
const manifest = { name: '@maka/core', type: 'module', exports: { './session': './dist/session.js' } };
await writeFile(join(core, 'package.json'), JSON.stringify(manifest));
await writeFile(join(core, 'dist/session.js'), 'export const session = 1;');
await writeFile(join(root, 'entry.js'), "export { session } from '@maka/core/session';");

server = await createServer({
configFile: false,
root,
logLevel: 'silent',
server: { host: '127.0.0.1', port: 0 },
optimizeDeps: { noDiscovery: true, include: [] },
plugins: [workspacePackagesPlugin(repoRoot)],
});
await server.listen();
const url = server.resolvedUrls.local[0];
assert.equal((await fetch(`${url}entry.js`)).status, 200);

// The workspace build has emitted the new module before the manifest changes.
await writeFile(join(core, 'dist/workhub-session-resolver.js'), 'export const resolver = 2;');
manifest.exports['./workhub-session-resolver'] = './dist/workhub-session-resolver.js';
await writeFile(join(core, 'package.json'), JSON.stringify(manifest));
await writeFile(join(root, 'entry.js'), "export { resolver } from '@maka/core/workhub-session-resolver';");

let failure;
const deadline = Date.now() + 5000;
while (Date.now() < deadline) {
try {
const response = await fetch(`${url}entry.js`);
const body = await response.text();
assert.equal(response.status, 200, body);
assert.match(body, /\/packages\/core\/dist\/workhub-session-resolver\.js/);
return;
} catch (error) {
failure = error;
await setTimeout(50);
}
}
throw failure;
});
39 changes: 39 additions & 0 deletions apps/desktop/vite-workspace-packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { normalizePath, type Plugin } from 'vite';

export function workspacePackagesPlugin(repoRoot: string): Plugin {
return {
name: 'maka-workspace-packages',
apply: 'serve',
configResolved(config) {
const manifest = resolve(repoRoot, 'package.json');
const { workspaces } = JSON.parse(readFileSync(manifest, 'utf8')) as { workspaces: string[] };
// Workspace exports change resolution just like Vite config does. A file
// watch alone leaves the native resolver's package cache stale.
config.configFileDependencies.push(
normalizePath(manifest),
...workspaces.map((workspace) => normalizePath(resolve(repoRoot, workspace, 'package.json'))),
);
},
};
}
2 changes: 2 additions & 0 deletions apps/desktop/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import react from '@vitejs/plugin-react';
import { dependencyPatchesCachePlugin } from './vite-dependency-patches.js';
import { bundledNpmPackagesPlugin } from './vite-bundled-packages.js';
import { rendererEntryContractPlugin } from './scripts/vite-renderer-entry-contract.js';
import { workspacePackagesPlugin } from './vite-workspace-packages.js';

/**
* PR-ICONS-FULL-REPLACE-0 (WAWQAQ msg `60064e2d` 2026-06-24): point the
Expand All @@ -46,6 +47,7 @@ export default defineConfig({
plugins: [
react(),
dependencyPatchesCachePlugin(REPO_ROOT),
workspacePackagesPlugin(REPO_ROOT),
bundledNpmPackagesPlugin(),
rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer')),
],
Expand Down