proteum 2.2.3 → 2.2.7

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.
@@ -22,7 +22,6 @@ import {
22
22
 
23
23
  // Configs
24
24
  import Compiler from '../compiler';
25
- import { regex } from '../compiler/common';
26
25
  import { createDevEventServer } from './devEvents';
27
26
  import { renderDevSession, renderServerReadyBanner, renderDevShutdownBanner } from '../presentation/devSession';
28
27
  import { clearInteractiveConsole } from '../presentation/welcome';
@@ -41,8 +40,7 @@ import {
41
40
  } from '../runtime/devSessions';
42
41
  import { resolveFrameworkInstallInfo } from '../paths';
43
42
  import { logVerbose } from '../runtime/verbose';
44
- import { inspectProjectAgentFiles } from '../utils/agents';
45
- import { runConfigureAgentsWizard } from './configure';
43
+ import { configureProjectAgentInstructions, resolveProjectAgentMonorepoRoot } from '../utils/agents';
46
44
 
47
45
  // Core
48
46
  import { app, App } from '../app';
@@ -61,6 +59,7 @@ const hotReloadableServerPathPatterns = [
61
59
  /^server\/services\/.+\.controller\.[jt]sx?$/,
62
60
  ];
63
61
  const hotReloadableRoots = [() => app.paths.root, () => cli.paths.core.root];
62
+ const transpileSourceWatchPattern = /\.(ts|tsx|js|jsx|css|less|scss)$/;
64
63
 
65
64
  /*----------------------------------
66
65
  - STATE
@@ -144,43 +143,74 @@ const createIgnoredWatchMatcher = (outputPaths: string[]) => (watchPath: string)
144
143
  return ignoredWatchPathPatterns.test(normalizedWatchPath);
145
144
  };
146
145
 
147
- const promptToConfigureAgentsIfMissing = async () => {
148
- if (cli.args.json === true) return;
149
- if (!process.stdin.isTTY || !process.stdout.isTTY) return;
150
-
151
- const inspection = inspectProjectAgentFiles({ appRoot: app.paths.root });
152
- if (!inspection.missing.includes('AGENTS.md')) return;
146
+ const promptBlockedAgentInstructionOverwrites = async (blockedPaths: string[]) => {
147
+ if (blockedPaths.length === 0) return [];
148
+ if (cli.args.json === true || !process.stdin.isTTY || !process.stdout.isTTY) {
149
+ throw new UsageError(
150
+ [
151
+ 'Proteum could not update managed instruction files because existing paths are blocked:',
152
+ ...blockedPaths.map((entry) => `- ${entry}`),
153
+ 'Run `proteum configure agents` in an interactive terminal to choose which paths can be replaced.',
154
+ ].join('\n'),
155
+ );
156
+ }
153
157
 
154
- console.info(await renderWarning('Proteum could not find the app-root `AGENTS.md` instruction file.'));
158
+ console.info(await renderWarning('Proteum found existing paths that block managed instruction updates.'));
155
159
  console.info(
156
160
  [
157
- 'Missing standard AGENTS files:',
158
- ...inspection.missing.map((entry) => `- ${entry}`),
159
- '',
160
- 'Run `proteum configure agents` now before starting the dev server?',
161
+ 'Choose whether to overwrite each blocked path with a tracked Proteum instruction file:',
162
+ ...blockedPaths.map((entry) => `- ${entry}`),
161
163
  ].join('\n'),
162
164
  );
163
165
 
164
- const response = await prompts(
165
- {
166
- type: 'confirm',
167
- name: 'value',
168
- message: 'Run the configure-agents wizard now?',
169
- initial: true,
170
- },
171
- {
172
- onCancel: () => {
173
- throw new UsageError('Cancelled `proteum dev`.');
166
+ const overwriteBlockedPaths: string[] = [];
167
+
168
+ for (const blockedPath of blockedPaths) {
169
+ const response = await prompts(
170
+ {
171
+ type: 'confirm',
172
+ name: 'value',
173
+ message: `Overwrite ${blockedPath}?`,
174
+ initial: false,
174
175
  },
175
- },
176
- );
176
+ {
177
+ onCancel: () => {
178
+ throw new UsageError('Cancelled `proteum dev`.');
179
+ },
180
+ },
181
+ );
182
+
183
+ if (response.value === true) overwriteBlockedPaths.push(blockedPath);
184
+ }
177
185
 
178
- if (response.value !== true) return;
186
+ return overwriteBlockedPaths;
187
+ };
188
+
189
+ const ensureProjectAgentInstructions = async () => {
190
+ const monorepoRoot = resolveProjectAgentMonorepoRoot(app.paths.root);
191
+ const preview = configureProjectAgentInstructions({
192
+ appRoot: app.paths.root,
193
+ coreRoot: cli.paths.core.root,
194
+ dryRun: true,
195
+ monorepoRoot,
196
+ });
197
+ const overwriteBlockedPaths = await promptBlockedAgentInstructionOverwrites(preview.blocked);
179
198
 
180
- await runConfigureAgentsWizard({
199
+ const result = configureProjectAgentInstructions({
181
200
  appRoot: app.paths.root,
182
201
  coreRoot: cli.paths.core.root,
202
+ monorepoRoot,
203
+ overwriteBlockedPaths,
183
204
  });
205
+
206
+ if (result.blocked.length === 0) return;
207
+
208
+ throw new UsageError(
209
+ [
210
+ 'Proteum could not update all managed instruction files because these paths were left blocked:',
211
+ ...result.blocked.map((entry) => `- ${entry}`),
212
+ ].join('\n'),
213
+ );
184
214
  };
185
215
 
186
216
  const getDevAppName = (app: App) =>
@@ -530,11 +560,11 @@ type TIndexedSourceWatchEvent = 'change' | 'rename';
530
560
  type TIndexedSourceWatchCompilerName = 'server' | 'client';
531
561
  type TIndexedSourceWatchInvalidateTarget = 'all' | TIndexedSourceWatchCompilerName;
532
562
  type TIndexedSourceWatchRule = {
533
- compilerName: TIndexedSourceWatchCompilerName;
563
+ compilerNames: TIndexedSourceWatchCompilerName[];
534
564
  rootPath: string;
535
565
  relativePathPattern: RegExp;
536
566
  eventTypes: TIndexedSourceWatchEvent[];
537
- invalidateTarget: TIndexedSourceWatchInvalidateTarget;
567
+ invalidateTargets: TIndexedSourceWatchInvalidateTarget[];
538
568
  };
539
569
  type TNamedWatching = { compiler: { name?: string }; invalidate: () => void };
540
570
  type TMultiWatchingLike = TDevWatching & { watchings?: TNamedWatching[] };
@@ -553,26 +583,26 @@ const resolveIndexedSourceWatchRules = (): TIndexedSourceWatchRule[] => {
553
583
 
554
584
  return [
555
585
  {
556
- compilerName: 'server',
586
+ compilerNames: ['server'],
557
587
  rootPath: app.paths.root,
558
588
  relativePathPattern: /^commands(?:\/|$)/,
559
589
  eventTypes: ['rename'],
560
- invalidateTarget: 'all',
590
+ invalidateTargets: ['all'],
561
591
  },
562
592
  {
563
- compilerName: 'server',
593
+ compilerNames: ['server'],
564
594
  rootPath: cli.paths.core.root,
565
595
  relativePathPattern: /^commands(?:\/|$)/,
566
596
  eventTypes: ['rename'],
567
- invalidateTarget: 'all',
597
+ invalidateTargets: ['all'],
568
598
  },
569
599
  ...transpileWatchRoots.map(
570
600
  (rootPath): TIndexedSourceWatchRule => ({
571
- compilerName: 'server',
601
+ compilerNames: ['client', 'server'],
572
602
  rootPath,
573
- relativePathPattern: regex.scripts,
603
+ relativePathPattern: transpileSourceWatchPattern,
574
604
  eventTypes: ['change', 'rename'],
575
- invalidateTarget: 'server',
605
+ invalidateTargets: ['client', 'server'],
576
606
  }),
577
607
  ),
578
608
  ];
@@ -587,6 +617,12 @@ const findCompilerWatching = (
587
617
  return childWatchings?.find((childWatching) => childWatching.compiler.name === compilerName);
588
618
  };
589
619
 
620
+ const formatInvalidateTargets = (invalidateTargets: TIndexedSourceWatchCompilerName[]) => {
621
+ if (invalidateTargets.length === 1) return invalidateTargets[0];
622
+ if (invalidateTargets.length === 2) return `${invalidateTargets[0]} and ${invalidateTargets[1]}`;
623
+ return `${invalidateTargets.slice(0, -1).join(', ')}, and ${invalidateTargets[invalidateTargets.length - 1]}`;
624
+ };
625
+
590
626
  const closeFsWatcher = async (watcher: FSWatcher) => {
591
627
  await new Promise<void>((resolve) => {
592
628
  watcher.once('close', () => resolve());
@@ -618,7 +654,7 @@ const createIndexedSourceWatching = ({
618
654
 
619
655
  if (pendingInvalidateTargets.has('all')) {
620
656
  pendingInvalidateTargets.clear();
621
- logVerbose('Indexed source files changed. Invalidating the dev compiler to refresh generated artifacts.');
657
+ logVerbose('Indexed source files changed. Invalidating all dev compilers to refresh generated artifacts.');
622
658
  watching.invalidate();
623
659
  return;
624
660
  }
@@ -630,40 +666,57 @@ const createIndexedSourceWatching = ({
630
666
 
631
667
  if (invalidateTargets.length === 0) return;
632
668
 
633
- logVerbose('Transpiled source files changed. Invalidating the server compiler to refresh mutable package code.');
669
+ const compilerWatchings: TNamedWatching[] = [];
670
+
634
671
  for (const invalidateTarget of invalidateTargets) {
635
672
  const compilerWatching = findCompilerWatching(watching, invalidateTarget);
636
673
 
637
674
  if (!compilerWatching) {
675
+ logVerbose('Transpiled source files changed. Invalidating all dev compilers to refresh mutable package code.');
638
676
  watching.invalidate();
639
677
  return;
640
678
  }
641
679
 
680
+ compilerWatchings.push(compilerWatching);
681
+ }
682
+
683
+ logVerbose(
684
+ `Transpiled source files changed. Invalidating ${formatInvalidateTargets(
685
+ invalidateTargets,
686
+ )} compilers to refresh mutable package code.`,
687
+ );
688
+ for (const compilerWatching of compilerWatchings) {
642
689
  compilerWatching.invalidate();
643
690
  }
644
691
  };
645
692
 
646
693
  const queueInvalidate = ({
647
- compilerName,
694
+ compilerNames,
648
695
  filepath,
649
- invalidateTarget,
696
+ invalidateTargets,
650
697
  }: {
651
- compilerName: TIndexedSourceWatchCompilerName;
698
+ compilerNames: TIndexedSourceWatchCompilerName[];
652
699
  filepath: string;
653
- invalidateTarget: TIndexedSourceWatchInvalidateTarget;
700
+ invalidateTargets: TIndexedSourceWatchInvalidateTarget[];
654
701
  }) => {
655
702
  const normalizedFilepath = normalizeWatchPath(filepath);
656
- const queueKey = `${invalidateTarget}:${compilerName}:${normalizedFilepath}`;
703
+ const queueKey = `${invalidateTargets.join(',')}:${compilerNames.join(',')}:${normalizedFilepath}`;
657
704
  const queuedAt = recentQueuedChanges.get(queueKey);
658
705
 
659
706
  if (queuedAt !== undefined && Date.now() - queuedAt < 250) return;
660
707
 
661
708
  recentQueuedChanges.set(queueKey, Date.now());
662
- const changedFiles = pendingChanges.get(compilerName) || new Set<string>();
663
709
 
664
- changedFiles.add(normalizedFilepath);
665
- pendingChanges.set(compilerName, changedFiles);
666
- pendingInvalidateTargets.add(invalidateTarget);
710
+ for (const compilerName of compilerNames) {
711
+ const changedFiles = pendingChanges.get(compilerName) || new Set<string>();
712
+
713
+ changedFiles.add(normalizedFilepath);
714
+ pendingChanges.set(compilerName, changedFiles);
715
+ }
716
+
717
+ for (const invalidateTarget of invalidateTargets) {
718
+ pendingInvalidateTargets.add(invalidateTarget);
719
+ }
667
720
 
668
721
  if (invalidateTimer) return;
669
722
  invalidateTimer = setTimeout(flushInvalidate, 40);
@@ -682,9 +735,9 @@ const createIndexedSourceWatching = ({
682
735
  if (!watchRule.eventTypes.includes(normalizedEventType) && relativePath) return;
683
736
 
684
737
  queueInvalidate({
685
- compilerName: watchRule.compilerName,
738
+ compilerNames: watchRule.compilerNames,
686
739
  filepath: relativePath ? path.join(rootPath, relativePath) : rootPath,
687
- invalidateTarget: watchRule.invalidateTarget,
740
+ invalidateTargets: watchRule.invalidateTargets,
688
741
  });
689
742
  }),
690
743
  );
@@ -755,7 +808,7 @@ const runDevLoop = async () => {
755
808
  // - Node modules except 5HTP core (framework dev mode)
756
809
  // - Generated files during runtime (cause infinite loop. Ex: models.d.ts)
757
810
  // - Webpack output folders (`./dev`, legacy `./bin`)
758
- ignored: ignoredWatchMatcher,
811
+ ignored: ignoredWatchMatcher as never,
759
812
 
760
813
  //aggregateTimeout: 1000,
761
814
  },
@@ -880,6 +933,6 @@ export const run = async () => {
880
933
  return;
881
934
  }
882
935
 
883
- await promptToConfigureAgentsIfMissing();
936
+ await ensureProjectAgentInstructions();
884
937
  await runDevLoop();
885
938
  };
@@ -5,7 +5,6 @@ import app from '../../app';
5
5
  import cli from '../..';
6
6
  import { inspectProteumEnv } from '../../../common/env/proteumEnv';
7
7
  import { reservedRouteOptionKeys, routeOptionKeys } from '../../../common/router/pageData';
8
- import { getProjectInstructionGitignoreEntries } from '../../utils/agents';
9
8
  import {
10
9
  TProteumManifest,
11
10
  TProteumManifestCommand,
@@ -40,10 +39,7 @@ const collectManifestDiagnostics = ({
40
39
  routes: TProteumManifest['routes'];
41
40
  }) => {
42
41
  const diagnostics: TProteumManifestDiagnostic[] = [];
43
- const expectedGitignoreEntries = [
44
- ...requiredGitignoreEntries,
45
- ...getProjectInstructionGitignoreEntries({ coreRoot: cli.paths.core.root }),
46
- ];
42
+ const expectedGitignoreEntries = [...requiredGitignoreEntries];
47
43
 
48
44
  const pushDiagnostic = (diagnostic: TProteumManifestDiagnostic) => {
49
45
  diagnostics.push(diagnostic);
@@ -113,22 +113,22 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
113
113
  configure: {
114
114
  name: 'configure',
115
115
  category: 'Project scaffolding',
116
- summary: 'Interactively configure Proteum-managed instruction stubs for a standalone app or monorepo app root.',
116
+ summary: 'Interactively configure tracked Proteum instruction files for a standalone app or monorepo app root.',
117
117
  usage: 'proteum configure agents',
118
118
  bestFor:
119
- 'Creating or switching the managed `AGENTS.md` instruction layout intentionally instead of having `init` or `dev` write instruction files implicitly.',
119
+ 'Creating or switching the tracked instruction layout intentionally while keeping Proteum-owned instructions embedded in managed sections.',
120
120
  examples: [
121
121
  {
122
- description: 'Configure instruction stubs for the current standalone app',
122
+ description: 'Configure instruction files for the current standalone app',
123
123
  command: 'proteum configure agents',
124
124
  },
125
125
  ],
126
126
  notes: [
127
- 'This command is interactive. It asks whether the current Proteum app belongs to a monorepo and, if so, which ancestor path should receive the reusable root `AGENTS.md` stub.',
128
- 'Standalone mode writes the full app-root instruction set into the current Proteum app root.',
129
- 'Monorepo mode writes the reusable root `AGENTS.md` into the chosen monorepo root and switches the current app root `AGENTS.md` to the app-root addendum.',
130
- 'If a target path already contains a non-managed file or foreign symlink, the interactive flow asks whether to overwrite it with the Proteum-managed stub.',
131
- 'Declined non-managed paths are left untouched; Proteum still creates missing stubs and updates stubs or symlinks it already manages.',
127
+ 'This command is interactive. It asks whether the current Proteum app belongs to a monorepo and, if so, which ancestor path should receive the reusable root `AGENTS.md` file.',
128
+ 'Standalone mode writes tracked instruction files into the current Proteum app root.',
129
+ 'Monorepo mode writes the reusable root `AGENTS.md` into the chosen monorepo root and the app-root instruction files into the current Proteum app root.',
130
+ 'Every managed instruction file contains a `# Proteum Instructions` section with the full embedded Proteum project instruction corpus.',
131
+ 'Existing content outside `# Proteum Instructions` is preserved. Directories and foreign symlinks are replaced only after confirmation.',
132
132
  ],
133
133
  status: 'experimental',
134
134
  },
@@ -191,7 +191,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
191
191
  notes: [
192
192
  'Use `--cwd` when the target Proteum app lives in another worktree or checkout and you do not want to `cd` first.',
193
193
  'Proteum writes a machine-readable dev session file under `var/run/proteum/dev/<port>.json` by default; override it with `--session-file` when an agent needs a stable path.',
194
- 'Before the interactive dev loop starts, Proteum offers to launch `proteum configure agents` when the app root is missing `AGENTS.md`.',
194
+ 'Before the dev loop starts, Proteum ensures tracked instruction files contain the current managed `# Proteum Instructions` section.',
195
195
  'Use `--replace-existing` when retries should stop the previously tracked matching session before starting a new one.',
196
196
  '`proteum dev list` inspects tracked sessions for the current app root. Add `--stale` to show only orphaned or dead sessions.',
197
197
  '`proteum dev stop` targets the current session file by default. Add `--all` to stop every tracked session for the current app root.',
@@ -139,7 +139,7 @@ export const renderCliOverview = async ({
139
139
  indent: ' ',
140
140
  nextIndent: ' ',
141
141
  }),
142
- wrapText('When the app root is missing `AGENTS.md`, the interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.', {
142
+ wrapText('Before the dev loop starts, `proteum dev` ensures tracked instruction files contain the current managed `# Proteum Instructions` section.', {
143
143
  indent: ' ',
144
144
  nextIndent: ' ',
145
145
  }),
@@ -5,7 +5,6 @@ import { UsageError } from 'clipanion';
5
5
 
6
6
  import cli from '..';
7
7
  import { loadApplicationIdentityConfig } from '../../common/applicationConfigLoader';
8
- import { renderProjectInstructionGitignoreBlock } from '../utils/agents';
9
8
  import { runProcess } from '../utils/runProcess';
10
9
  import {
11
10
  createClientTsconfigTemplate,
@@ -656,9 +655,7 @@ const createInitFilePlans = ({ appRoot, config }: { appRoot: string; config: TSc
656
655
  },
657
656
  {
658
657
  relativePath: '.gitignore',
659
- content: createGitignoreTemplate({
660
- projectInstructionGitignoreBlock: renderProjectInstructionGitignoreBlock({ coreRoot: cli.paths.core.root }),
661
- }),
658
+ content: createGitignoreTemplate(),
662
659
  },
663
660
  {
664
661
  relativePath: 'eslint.config.mjs',
@@ -727,7 +724,7 @@ export const runInitScaffold = async () => {
727
724
  ? 'Run `npm run dev` in the new app directory.'
728
725
  : 'Run `npm install`, then `npm run dev` in the new app directory.',
729
726
  );
730
- result.nextSteps.push('Run `proteum configure agents` when you want Proteum-managed instruction stubs.');
727
+ result.nextSteps.push('Run `proteum configure agents` when you want tracked Proteum instruction files.');
731
728
  result.nextSteps.push('Use `proteum create page|controller|command|route|service ...` to add app artifacts.');
732
729
 
733
730
  printResult(result, createInitSummary(result, config));
@@ -253,11 +253,7 @@ export const createServerTsconfigTemplate = (paths: TTsconfigTemplatePaths) => `
253
253
  }
254
254
  `;
255
255
 
256
- export const createGitignoreTemplate = ({
257
- projectInstructionGitignoreBlock,
258
- }: {
259
- projectInstructionGitignoreBlock: string;
260
- }) => `node_modules
256
+ export const createGitignoreTemplate = () => `node_modules
261
257
  /.proteum
262
258
  /.cache
263
259
  /bin
@@ -265,8 +261,6 @@ export const createGitignoreTemplate = ({
265
261
  /var
266
262
  /proteum.connected.json
267
263
  .env
268
-
269
- ${projectInstructionGitignoreBlock}
270
264
  `;
271
265
 
272
266
  export const createEnvTemplate = ({ port, url }: { port: number; url: string }) => `ENV_NAME=local