proteum 2.5.7 → 2.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  import path from 'path';
7
7
  import { spawn, ChildProcess } from 'child_process';
8
8
  import fs from 'fs-extra';
9
- import type { FSWatcher } from 'fs';
9
+ import { realpathSync, watch, type FSWatcher } from 'fs';
10
10
  import prompts from 'prompts';
11
11
  import { UsageError } from 'clipanion';
12
12
 
@@ -677,6 +677,16 @@ function normalizeWatchPath(watchPath: string) {
677
677
  return path.resolve(watchPath).replace(/\\/g, '/').replace(/\/$/, '');
678
678
  }
679
679
 
680
+ const resolveWatchPathAliases = (watchPath: string) => {
681
+ const aliases = new Set([normalizeWatchPath(watchPath)]);
682
+
683
+ try {
684
+ aliases.add(normalizeWatchPath(realpathSync(watchPath)));
685
+ } catch {}
686
+
687
+ return [...aliases];
688
+ };
689
+
680
690
  type TIndexedSourceWatchEvent = 'change' | 'rename';
681
691
  type TIndexedSourceWatchCompilerName = 'server' | 'client';
682
692
  type TIndexedSourceWatchInvalidateTarget = 'all' | TIndexedSourceWatchCompilerName;
@@ -692,14 +702,7 @@ type TMultiWatchingLike = TDevWatching & { watchings?: TNamedWatching[] };
692
702
 
693
703
  const resolveIndexedSourceWatchRules = (): TIndexedSourceWatchRule[] => {
694
704
  const transpileWatchRoots = app.transpileModuleDirectories
695
- .map((rootPath) => {
696
- try {
697
- return fs.realpathSync(rootPath);
698
- } catch {
699
- return rootPath;
700
- }
701
- })
702
- .map(normalizeWatchPath)
705
+ .flatMap((rootPath) => resolveWatchPathAliases(rootPath))
703
706
  .filter((rootPath, index, list) => list.indexOf(rootPath) === index);
704
707
 
705
708
  return [
@@ -723,7 +726,7 @@ const resolveIndexedSourceWatchRules = (): TIndexedSourceWatchRule[] => {
723
726
  rootPath,
724
727
  relativePathPattern: transpileSourceWatchPattern,
725
728
  eventTypes: ['change', 'rename'],
726
- invalidateTargets: ['client', 'server'],
729
+ invalidateTargets: ['all'],
727
730
  }),
728
731
  ),
729
732
  ];
@@ -848,7 +851,7 @@ const createIndexedSourceWatching = ({
848
851
  if (!fs.existsSync(rootPath)) continue;
849
852
 
850
853
  watchers.push(
851
- fs.watch(rootPath, { recursive: true }, (eventType, filename) => {
854
+ watch(rootPath, { recursive: true }, (eventType, filename) => {
852
855
  const relativePath = typeof filename === 'string' ? filename.replace(/\\/g, '/').replace(/^\.\//, '') : '';
853
856
  const normalizedEventType: TIndexedSourceWatchEvent = eventType === 'change' ? 'change' : 'rename';
854
857
 
@@ -44,10 +44,12 @@ export default function createCommonConfig(
44
44
  outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin',
45
45
  ): Configuration {
46
46
  const dev = mode === 'dev';
47
- const enableFilesystemCache = dev ? cli.args.cache !== false : cli.args.cache === true;
48
47
  const transpileModuleDirectories = app.transpileModuleDirectories;
48
+ const hasTranspileModuleDirectories = transpileModuleDirectories.length > 0;
49
+ // Persistent cache can restore stale modules from mutable workspace packages even after manual invalidation.
50
+ const enableFilesystemCache = dev ? cli.args.cache !== false && !hasTranspileModuleDirectories : cli.args.cache === true;
49
51
  const transpileModuleSnapshot =
50
- dev && transpileModuleDirectories.length > 0
52
+ dev && hasTranspileModuleDirectories
51
53
  ? {
52
54
  // Transpiled local packages can resolve through node_modules symlinks,
53
55
  // but they still need live invalidation like mutable app sources in dev.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proteum",
3
3
  "description": "LLM-first Opinionated Typescript Framework for web applications.",
4
- "version": "2.5.7",
4
+ "version": "2.5.8",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",
@@ -103,6 +103,21 @@ const waitForAssetContaining = async (appRoot, extension, marker, timeoutMs = 60
103
103
  throw new Error(`Timed out waiting for ${extension} asset containing ${marker}.`);
104
104
  };
105
105
 
106
+ const waitForBodyContaining = async (port, urlPath, marker, timeoutMs = 60000) => {
107
+ const deadline = Date.now() + timeoutMs;
108
+
109
+ while (Date.now() < deadline) {
110
+ try {
111
+ const { body } = await request(port, urlPath, { Accept: 'text/html' });
112
+ if (body.includes(marker)) return body;
113
+ } catch {}
114
+
115
+ await sleep(250);
116
+ }
117
+
118
+ throw new Error(`Timed out waiting for ${urlPath} body containing ${marker}.`);
119
+ };
120
+
106
121
  const waitForSessionReady = async (sessionFile, child, getOutput, timeoutMs = 90000) => {
107
122
  const deadline = Date.now() + timeoutMs;
108
123
 
@@ -199,8 +214,13 @@ const createSharedStyleSource = (marker) => `.shared-style-marker {
199
214
  `;
200
215
 
201
216
  const createFixture = (root, port, options = {}) => {
202
- const appRoot = path.join(root, 'app');
203
- const sharedRoot = path.join(root, 'shared');
217
+ const monorepoRootInstall = options.monorepoRootInstall === true;
218
+ const appRoot = monorepoRootInstall ? path.join(root, 'apps', 'app') : path.join(root, 'app');
219
+ const sharedRoot = monorepoRootInstall ? path.join(root, 'packages', 'shared') : path.join(root, 'shared');
220
+ const sharedDependency = monorepoRootInstall ? 'file:../../packages/shared' : 'file:../shared';
221
+ const sharedInstallRoot = monorepoRootInstall
222
+ ? path.join(root, 'node_modules', '@test', 'shared')
223
+ : path.join(appRoot, 'node_modules', '@test', 'shared');
204
224
  const cacheConfigSource = options.routerCache ? ` cache: ${options.routerCache},\n` : '';
205
225
 
206
226
  fs.mkdirSync(path.join(appRoot, 'public'), { recursive: true });
@@ -217,7 +237,7 @@ const createFixture = (root, port, options = {}) => {
217
237
  private: true,
218
238
  version: '0.0.0',
219
239
  dependencies: {
220
- '@test/shared': 'file:../shared',
240
+ '@test/shared': sharedDependency,
221
241
  proteum: `file:${coreRoot}`,
222
242
  },
223
243
  },
@@ -474,7 +494,7 @@ export default definePageRoute({
474
494
  writeFile(path.join(sharedRoot, 'styles.css'), createSharedStyleSource('STYLE_MARKER_INITIAL'));
475
495
 
476
496
  createSymlink(coreRoot, path.join(appRoot, 'node_modules', 'proteum'));
477
- createSymlink(sharedRoot, path.join(appRoot, 'node_modules', '@test', 'shared'));
497
+ createSymlink(sharedRoot, sharedInstallRoot);
478
498
 
479
499
  return {
480
500
  appRoot,
@@ -500,11 +520,14 @@ const stopDevServer = async (child) => {
500
520
  });
501
521
  };
502
522
 
503
- const startDevServer = (appRoot, port, sessionFile) => {
523
+ const startDevServer = (appRoot, port, sessionFile, options = {}) => {
504
524
  let output = '';
525
+ const args = [cliBin, 'dev', '--cwd', appRoot, '--port', String(port), '--session-file', sessionFile];
526
+ if (options.noCache !== false) args.push('--no-cache');
527
+ args.push('--verbose');
505
528
  const child = spawn(
506
529
  process.execPath,
507
- [cliBin, 'dev', '--cwd', appRoot, '--port', String(port), '--session-file', sessionFile, '--no-cache', '--verbose'],
530
+ args,
508
531
  {
509
532
  cwd: appRoot,
510
533
  env: {
@@ -577,6 +600,41 @@ test('proteum dev invalidates client assets and reloads for transpiled package s
577
600
  }
578
601
  });
579
602
 
603
+ test(
604
+ 'proteum dev invalidates SSR and client assets for monorepo-root transpiled package installs',
605
+ { timeout: 180000 },
606
+ async () => {
607
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-monorepo-transpile-watch-'));
608
+ const port = await resolvePortPair();
609
+ const { appRoot, sharedRoot } = createFixture(root, port, { monorepoRootInstall: true });
610
+ const sessionFile = path.join(appRoot, 'var', 'run', 'proteum', 'dev', 'monorepo-transpile-watch-test.json');
611
+ const { child, getOutput } = startDevServer(appRoot, port, sessionFile, { noCache: false });
612
+
613
+ try {
614
+ await waitForSessionReady(sessionFile, child, getOutput);
615
+ await waitForBodyContaining(port, '/', 'SCRIPT_MARKER_INITIAL').catch((error) => {
616
+ throw new Error(`${error.message}\n${getOutput()}`);
617
+ });
618
+ await waitForAssetContaining(appRoot, '.js', 'SCRIPT_MARKER_INITIAL').catch((error) => {
619
+ throw new Error(`${error.message}\n${getOutput()}`);
620
+ });
621
+
622
+ const reloadStream = await connectToReloadStream(port + 1);
623
+ writeFile(path.join(sharedRoot, 'index.tsx'), createSharedIndexSource('SCRIPT_MARKER_MONOREPO_UPDATED'));
624
+
625
+ await waitForAssetContaining(appRoot, '.js', 'SCRIPT_MARKER_MONOREPO_UPDATED');
626
+ await waitForBodyContaining(port, '/', 'SCRIPT_MARKER_MONOREPO_UPDATED');
627
+ const reloadEvent = await reloadStream.waitForReload();
628
+ reloadStream.close();
629
+
630
+ assert.equal(reloadEvent.type, 'reload');
631
+ } finally {
632
+ await stopDevServer(child);
633
+ fs.rmSync(root, { recursive: true, force: true });
634
+ }
635
+ },
636
+ );
637
+
580
638
  test('proteum dev applies router HTTP cache config to HTML and public assets', { timeout: 180000 }, async () => {
581
639
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-router-cache-'));
582
640
  const port = await resolvePortPair();