chore(ts-migration): resolve source-text reads across .js/.jsx/.ts/.tsx
The check suite consumes source files through two entirely separate channels, and Phase 0 only hardened one of them. `withSourceResolution` wraps `ssrLoadModule`, which covers the 159 module loads. It does not and cannot see the other 30 sites, which read source as TEXT through `readFileSync` to assert structural facts - "the panel imports no local suggestion ranker", "no runtime path writes a definition". Renaming `base44Client.js` to `.ts` is what surfaced the difference: ENOENT in the middle of a suite that had been passing, from a line no grep for `ssrLoadModule` would ever have found. `resolveSourcePath()` in `ssr-resolve.mjs` reuses the existing `candidatesFor()` ordering - the written path first, then `.ts`, then `.tsx` - and returns a path relative to the root, so every call site keeps its `join(ROOT, ...)` as it was. An unresolvable path comes back unchanged, which keeps the two absence assertions honest: a check proving `store.js` is GONE still asks about the path it means, and now also notices if the file returns under another extension. Thirty-seven lines change, each a one-for-one replacement. No assertion text, no record() message, no ordering, no logic. The audit found the reads in four shapes, and two of them a path grep cannot see: - direct readFileSync(join(ROOT, 'src/x.jsx'), 'utf8') - via a const const P = join(ROOT, 'src/x.jsx') - dir + name ['node.js', 'patch.js'].map((f) => readFileSync(join(ROOT, 'src/lib/ui', f))) - path array for (const f of files) readFileSync(join(ROOT, f)) The third and fourth hide thirteen filenames in adjacent arrays, which is why the first estimate of this work was twenty-two files and the real number is thirty-five. One directory scan also filtered `/\.jsx?$/` over `src/pages/admin` and `src/components/agents`. That one does not crash - it quietly matches nothing once those directories are TypeScript, and the check passes having inspected an empty set. Widened to `/\.[jt]sx?$/`. A silent shrink is worse than a failure, and CI's FLOOR of 900 would not have caught it. Deliberately NOT touched, because they are correctly extension-specific: the `dist/assets` filter reads built bundles, which are `.js` whatever the source was; `scripts/stubs/react-hot-toast.js` and `scripts/browser-flows.js` are scripts, not migrated source; and every comment that mentions a `.js` filename says something true about a file that still has that name. Proven rather than assumed. `npm test` reports 1684/1691 before and after, the same seven failures. Then `src/api/base44Client.js` - the exact file whose rename broke the suite - was renamed to `.ts`, the suite re-run, and the check that reads it as text passed: "the app does not import the seed fixture". The rename was reverted and the file verified byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
@@ -19,7 +19,7 @@ import React from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { BASELINE_PATH, captureBaseline } from './owliver-capture.mjs';
|
||||
import { buildFixture, FIXTURE_PATH } from './seed-fixture.mjs';
|
||||
import { withSourceResolution } from './ssr-resolve.mjs';
|
||||
import { resolveSourcePath, withSourceResolution } from './ssr-resolve.mjs';
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const results = [];
|
||||
@@ -1618,10 +1618,10 @@ record('...and stops asking a backend that does not have the route',
|
||||
now does — and the failure mode of it returning is not an error but a second
|
||||
opinion silently outranking the server's. */
|
||||
record('the panel imports no local suggestion ranker',
|
||||
!existsSync(join(ROOT, 'src/components/ai-assistant/matchPrompts.js')),
|
||||
!existsSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/matchPrompts.js'))),
|
||||
'matchPrompts.js removed');
|
||||
|
||||
const panelSource = readFileSync(join(ROOT, 'src/components/ai-assistant/KrowAssistant.jsx'), 'utf8');
|
||||
const panelSource = readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/KrowAssistant.jsx')), 'utf8');
|
||||
record('...and does not rank, score or sort suggestions itself',
|
||||
!/rankPrompts|\.sort\(|score\(/.test(panelSource),
|
||||
'no ranking in the panel');
|
||||
@@ -1630,10 +1630,10 @@ record('...it asks the API instead',
|
||||
|
||||
/* The production data client must not carry the fixture. */
|
||||
record('the app does not import the seed fixture',
|
||||
!/from '\.\/seed'/.test(readFileSync(join(ROOT, 'src/api/base44Client.js'), 'utf8')),
|
||||
!/from '\.\/seed'/.test(readFileSync(join(ROOT, resolveSourcePath('src/api/base44Client.js')), 'utf8')),
|
||||
'base44Client reads demoUser.js, not seed.js');
|
||||
record('the local entity store is gone',
|
||||
!existsSync(join(ROOT, 'src/api/store.js')),
|
||||
!existsSync(join(ROOT, resolveSourcePath('src/api/store.js'))),
|
||||
'store.js removed');
|
||||
|
||||
/* ── 11. A fresh Add Owliver Skill screen ─────────────────────────────────
|
||||
@@ -2958,7 +2958,7 @@ const runtime = await server.ssrLoadModule('/src/lib/agents/runtime.js');
|
||||
})().pass);
|
||||
|
||||
record('ownership: the rule is the one the page actually uses', (() => {
|
||||
const source = readFileSync(join(ROOT, 'src/components/skills/SkillSurface.jsx'), 'utf8');
|
||||
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/skills/SkillSurface.jsx')), 'utf8');
|
||||
return {
|
||||
pass: /agentPermitsSkill\(skill\.id, scope\)/.test(source)
|
||||
&& /from '@\/lib\/agents\/runtime'/.test(source),
|
||||
@@ -2969,7 +2969,7 @@ const runtime = await server.ssrLoadModule('/src/lib/agents/runtime.js');
|
||||
record('ownership: the panel reads agents from the definitions store', (() => {
|
||||
/* The live failure: this file read `preferences.customAgents`, which authored
|
||||
agents had already moved out of, so no attachment was ever visible here. */
|
||||
const source = readFileSync(join(ROOT, 'src/components/ai-assistant/AgentContext.jsx'), 'utf8');
|
||||
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/AgentContext.jsx')), 'utf8');
|
||||
return {
|
||||
pass: /useAgentDefinitions\(\)/.test(source)
|
||||
&& /sourcesFrom\(/.test(source)
|
||||
@@ -3788,7 +3788,7 @@ record('agent starters carry no capability, so they cannot address an unoffered
|
||||
* Read out of `App.jsx` rather than asserted against a written list, so a route
|
||||
* that is renamed or removed fails here rather than in someone's browser.
|
||||
*/
|
||||
const appSource = readFileSync(join(ROOT, 'src/App.jsx'), 'utf8');
|
||||
const appSource = readFileSync(join(ROOT, resolveSourcePath('src/App.jsx')), 'utf8');
|
||||
|
||||
/**
|
||||
* The switcher, if there still is one.
|
||||
@@ -3800,7 +3800,7 @@ const appSource = readFileSync(join(ROOT, 'src/App.jsx'), 'utf8');
|
||||
* ENOENT and stopped the whole run here, taking every section after it with it.
|
||||
* Absent is a state to report, not to crash on.
|
||||
*/
|
||||
const SWITCHER_PATH = join(ROOT, 'src/components/ai-assistant/AgentSwitcher.jsx');
|
||||
const SWITCHER_PATH = join(ROOT, resolveSourcePath('src/components/ai-assistant/AgentSwitcher.jsx'));
|
||||
const switcherSource = existsSync(SWITCHER_PATH) ? readFileSync(SWITCHER_PATH, 'utf8') : null;
|
||||
|
||||
/* Only live navigations count: a disabled control goes nowhere by design. */
|
||||
@@ -3836,7 +3836,7 @@ record('every route the agent switcher navigates to exists',
|
||||
* keeping — offered somewhere, and the route resolves — so it is asserted
|
||||
* against where the control actually is.
|
||||
*/
|
||||
const agentsListSource = readFileSync(join(ROOT, 'src/pages/admin/WorkspaceAgents.jsx'), 'utf8');
|
||||
const agentsListSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/WorkspaceAgents.jsx')), 'utf8');
|
||||
record('creating an agent is reachable, and its screen exists',
|
||||
/navigate\('\/admin\/workspace\/agents\/new'\)/.test(agentsListSource)
|
||||
&& routed.has('/admin/workspace/agents/new'),
|
||||
@@ -4497,7 +4497,7 @@ record('every category offered by the picker matches at least one skill',
|
||||
|
||||
const managementRoutes = ['/admin/workspace/agents', '/admin/workspace/agents/new'];
|
||||
const appRoutes = new Set(
|
||||
[...readFileSync(join(ROOT, 'src/App.jsx'), 'utf8').matchAll(/<Route\s+path="([^"]+)"/g)]
|
||||
[...readFileSync(join(ROOT, resolveSourcePath('src/App.jsx')), 'utf8').matchAll(/<Route\s+path="([^"]+)"/g)]
|
||||
.map((m) => m[1])
|
||||
.map((path) => (path.startsWith('/') ? path : `/admin/${path}`))
|
||||
);
|
||||
@@ -4523,7 +4523,7 @@ record('every surface route is registered in the router',
|
||||
|
||||
record('the dynamic agent route is registered after the static one',
|
||||
(() => {
|
||||
const source = readFileSync(join(ROOT, 'src/App.jsx'), 'utf8');
|
||||
const source = readFileSync(join(ROOT, resolveSourcePath('src/App.jsx')), 'utf8');
|
||||
return source.indexOf('workspace/agents/new') < source.indexOf('workspace/agents/:id');
|
||||
})(),
|
||||
'so `agents/new` cannot be read as an agent whose id is "new"');
|
||||
@@ -4710,8 +4710,8 @@ record('...and never answered from the configure page itself',
|
||||
* Asserted structurally: the configure screen must not import or define a chat.
|
||||
* The panel it gets is the one the Admin shell already mounts.
|
||||
*/
|
||||
const detailSource = readFileSync(join(ROOT, 'src/pages/admin/AgentDetail.jsx'), 'utf8');
|
||||
const configureSource = readFileSync(join(ROOT, 'src/components/agents/AgentConfigure.jsx'), 'utf8');
|
||||
const detailSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/AgentDetail.jsx')), 'utf8');
|
||||
const configureSource = readFileSync(join(ROOT, resolveSourcePath('src/components/agents/AgentConfigure.jsx')), 'utf8');
|
||||
|
||||
/* The property is right and the old pattern was too blunt. It matched any
|
||||
occurrence of the substring, so `useAssistantPanel` — the hook for talking to
|
||||
@@ -4735,18 +4735,18 @@ record('the configure screen defines no chat of its own',
|
||||
record('the panel is mounted once, by the shell',
|
||||
/* `<AssistantPanel` alone also matches `<AssistantPanelProvider`, which is a
|
||||
different component and legitimately present. */
|
||||
(readFileSync(join(ROOT, 'src/layouts/AdminLayout.jsx'), 'utf8').match(/<AssistantPanel\b(?!Provider)/g) || []).length === 1);
|
||||
(readFileSync(join(ROOT, resolveSourcePath('src/layouts/AdminLayout.jsx')), 'utf8').match(/<AssistantPanel\b(?!Provider)/g) || []).length === 1);
|
||||
|
||||
/* The switcher is gone — see the note above. What replaced it is a label, and
|
||||
the property worth keeping is that it stayed one: a header that can be
|
||||
clicked is a second place to choose an agent. */
|
||||
const badgeSource = readFileSync(join(ROOT, 'src/components/ai-assistant/AgentBadge.jsx'), 'utf8');
|
||||
const badgeSource = readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/AgentBadge.jsx')), 'utf8');
|
||||
record('the panel header names the agent and does not switch it',
|
||||
!/<button|onClick=|navigate\(/.test(badgeSource),
|
||||
'a label, not a control');
|
||||
|
||||
record('PageContext is untouched',
|
||||
!/agentConfigure/.test(readFileSync(join(ROOT, 'src/components/ai-assistant/PageContext.jsx'), 'utf8')));
|
||||
!/agentConfigure/.test(readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/PageContext.jsx')), 'utf8')));
|
||||
|
||||
/* ── The configure surface declares no placements ───────────────────────── */
|
||||
|
||||
@@ -4781,7 +4781,7 @@ record('no skill declares the configure page',
|
||||
* **It stays Krow.** The palette is the tokens, and nothing else.
|
||||
*/
|
||||
|
||||
const canvasSource = readFileSync(join(ROOT, 'src/components/agents/AgentCanvas.jsx'), 'utf8');
|
||||
const canvasSource = readFileSync(join(ROOT, resolveSourcePath('src/components/agents/AgentCanvas.jsx')), 'utf8');
|
||||
const agentUi = detailSource + configureSource + canvasSource;
|
||||
|
||||
/* One surface with sections inside it, not four cards side by side. */
|
||||
@@ -4922,10 +4922,10 @@ const canvasImporters = [
|
||||
...readdirSync(join(ROOT, 'src/pages/admin')).map((f) => ['src/pages/admin', f]),
|
||||
...readdirSync(join(ROOT, 'src/components/agents')).map((f) => ['src/components/agents', f]),
|
||||
]
|
||||
.filter(([, f]) => /\.jsx?$/.test(f))
|
||||
.filter(([, f]) => /\.[jt]sx?$/.test(f))
|
||||
.filter(([, f]) => !/^(AgentConfigure|AgentDetail|AgentCanvas)\./.test(f))
|
||||
.filter(([dir, f]) => /agents\/AgentCanvas|from '\.\/AgentCanvas'/
|
||||
.test(readFileSync(join(ROOT, dir, f), 'utf8')))
|
||||
.test(readFileSync(join(ROOT, resolveSourcePath(`${dir}/${f}`)), 'utf8')))
|
||||
.map(([dir, f]) => `${dir}/${f}`);
|
||||
record('the workspace treatment is used by the configure screen alone',
|
||||
canvasImporters.length === 0,
|
||||
@@ -6045,8 +6045,8 @@ record('a candidate who scored zero still reaches the queue',
|
||||
/* Comments are stripped first: these assert what the page DOES, and a
|
||||
comment explaining the bug that was fixed must not read as the bug. */
|
||||
const decomment = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||||
const candidatesSrc = decomment(readFileSync(join(ROOT, 'src/pages/admin/Candidates.jsx'), 'utf8'));
|
||||
const nodesSrc = decomment(readFileSync(join(ROOT, 'src/pages/admin/candidates/nodes.jsx'), 'utf8'));
|
||||
const candidatesSrc = decomment(readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/Candidates.jsx')), 'utf8'));
|
||||
const nodesSrc = decomment(readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/candidates/nodes.jsx')), 'utf8'));
|
||||
/* Read from source rather than imported: the page pulls in the interview
|
||||
modal, which touches `window` at module scope, and standing up a DOM to
|
||||
read one string constant would be a worse test than reading the string. */
|
||||
@@ -6177,7 +6177,7 @@ const human = await server.ssrLoadModule('/src/lib/humanInterviews.js');
|
||||
* a real screening score with one this interview never took.
|
||||
*/
|
||||
{
|
||||
const hooksSrc = readFileSync(join(ROOT, 'src/lib/krowHooks.js'), 'utf8');
|
||||
const hooksSrc = readFileSync(join(ROOT, resolveSourcePath('src/lib/krowHooks.js')), 'utf8');
|
||||
const complete = hooksSrc.slice(
|
||||
hooksSrc.indexOf('export function useCompleteHumanInterview'),
|
||||
hooksSrc.indexOf('export function useRecordInterviewNotHeld')
|
||||
@@ -6223,7 +6223,7 @@ const human = await server.ssrLoadModule('/src/lib/humanInterviews.js');
|
||||
/* The AI flow is untouched: it still writes its own record at completion, and
|
||||
still does it through the same client this now shares. */
|
||||
{
|
||||
const aiSrc = readFileSync(join(ROOT, 'src/components/krow/AIInterviewModal.jsx'), 'utf8');
|
||||
const aiSrc = readFileSync(join(ROOT, resolveSourcePath('src/components/krow/AIInterviewModal.jsx')), 'utf8');
|
||||
record('the AI interview still records itself on completion',
|
||||
/const finishInterview = useCallback/.test(aiSrc)
|
||||
&& /createInterview\.mutateAsync\(\{/.test(aiSrc)
|
||||
@@ -6233,7 +6233,7 @@ const human = await server.ssrLoadModule('/src/lib/humanInterviews.js');
|
||||
|
||||
/* The modal is no longer theatre. */
|
||||
{
|
||||
const modalSrc = readFileSync(join(ROOT, 'src/components/krow/ScheduleInterviewModal.jsx'), 'utf8');
|
||||
const modalSrc = readFileSync(join(ROOT, resolveSourcePath('src/components/krow/ScheduleInterviewModal.jsx')), 'utf8');
|
||||
record('scheduling an interview persists it',
|
||||
/useScheduleHumanInterview/.test(modalSrc) && /schedule\.mutateAsync/.test(modalSrc));
|
||||
record('...the props its six call sites pass are unchanged',
|
||||
@@ -6250,8 +6250,8 @@ const human = await server.ssrLoadModule('/src/lib/humanInterviews.js');
|
||||
instead of to a person, and the hire it becomes cannot be traced back to the
|
||||
profile it came from. Both call sites are asserted because they were written
|
||||
at different times and only one of them is on the position page. */
|
||||
const positionDetailSource = readFileSync(join(ROOT, 'src/pages/PositionDetail.jsx'), 'utf8');
|
||||
const hooksSource = readFileSync(join(ROOT, 'src/lib/krowHooks.js'), 'utf8');
|
||||
const positionDetailSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/PositionDetail.jsx')), 'utf8');
|
||||
const hooksSource = readFileSync(join(ROOT, resolveSourcePath('src/lib/krowHooks.js')), 'utf8');
|
||||
record('admitting talent to a position links the application to the profile',
|
||||
/worker_profile_id: profile\.id/.test(positionDetailSource),
|
||||
'PositionDetail.admitTalent');
|
||||
@@ -6295,7 +6295,7 @@ record('...and asks for nobody when there is no profile',
|
||||
records.applicationsForProfile(whoApps, null).length === 0, 'empty, not everything');
|
||||
|
||||
/* The reported symptom: a talent pool you could read and not use. */
|
||||
const talentPoolSource = readFileSync(join(ROOT, 'src/pages/admin/talent-pool/nodes.jsx'), 'utf8');
|
||||
const talentPoolSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/talent-pool/nodes.jsx')), 'utf8');
|
||||
record('a talent pool row opens that person',
|
||||
/onRowClick=\{\(p\) => navigate\(`\/admin\/talent\/\$\{p\.id\}`\)\}/.test(talentPoolSource),
|
||||
'DataTable wires the handler only when it is given one');
|
||||
@@ -7695,7 +7695,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
...registryMod.nodeRegistry.list(),
|
||||
];
|
||||
const engine = ['node.js', 'registry.js', 'operations.js', 'validate.js', 'patch.js', 'inspect.js']
|
||||
.map((f) => readFileSync(join(ROOT, 'src/lib/ui', f), 'utf8'));
|
||||
.map((f) => readFileSync(join(ROOT, resolveSourcePath(`src/lib/ui/${f}`)), 'utf8'));
|
||||
|
||||
const offences = [];
|
||||
engine.forEach((source, i) => {
|
||||
@@ -7922,7 +7922,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
entry is only created by a `register` call that was handed a reference.
|
||||
A node naming a type nobody registered draws nothing — asserted above —
|
||||
so there is no string that can become code. */
|
||||
const source = readFileSync(join(ROOT, 'src/components/ui-tree/UiTreeRenderer.jsx'), 'utf8')
|
||||
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/ui-tree/UiTreeRenderer.jsx')), 'utf8')
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
.replace(/^\s*\/\/.*$/gm, '');
|
||||
return !/\bimport\s*\(/.test(source)
|
||||
@@ -8107,7 +8107,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
record('applying writes only the uiLayouts key, so no other preference moves', (() => {
|
||||
/* `useUiLayouts.save` sends `{ uiLayouts }` and nothing else; the endpoint
|
||||
shallow-merges, so `customSkills` and the rest survive untouched. */
|
||||
const source = readFileSync(join(ROOT, 'src/lib/krowHooks.js'), 'utf8');
|
||||
const source = readFileSync(join(ROOT, resolveSourcePath('src/lib/krowHooks.js')), 'utf8');
|
||||
const fn = source.slice(source.indexOf('export function useUiLayouts'), source.indexOf('export function useJobPostings'));
|
||||
const writes = [...fn.matchAll(/mutateAsync\(\{([^}]*)\}/g)].map((m) => m[1].trim());
|
||||
return writes.length === 1 && writes[0].startsWith('uiLayouts:');
|
||||
@@ -8207,7 +8207,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
record('an operation that would be refused is never stored', (() => {
|
||||
/* `propose` validates before it previews, so a refused change never becomes
|
||||
a preview and therefore never reaches an apply. */
|
||||
const provider = readFileSync(join(ROOT, 'src/components/ui-tree/UiEditingProvider.jsx'), 'utf8');
|
||||
const provider = readFileSync(join(ROOT, resolveSourcePath('src/components/ui-tree/UiEditingProvider.jsx')), 'utf8');
|
||||
const proposeBody = provider.slice(provider.indexOf('const propose'), provider.indexOf('/** Throw the experiment away'));
|
||||
return /applyOperation\(/.test(proposeBody)
|
||||
&& /if \(!result\.ok\)/.test(proposeBody)
|
||||
@@ -8225,7 +8225,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
];
|
||||
const offences = [];
|
||||
for (const f of files) {
|
||||
const code = readFileSync(join(ROOT, f), 'utf8')
|
||||
const code = readFileSync(join(ROOT, resolveSourcePath(f)), 'utf8')
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||||
for (const bad of ['writeFile', 'customSkills', 'skillDefinition', 'skill-definitions', '.md']) {
|
||||
if (code.includes(bad)) offences.push(`${f}: ${bad}`);
|
||||
@@ -8479,7 +8479,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
})().pass);
|
||||
|
||||
record('the language layer names no page and no component', (() => {
|
||||
const code = readFileSync(join(ROOT, 'src/lib/ui/intent.js'), 'utf8')
|
||||
const code = readFileSync(join(ROOT, resolveSourcePath('src/lib/ui/intent.js')), 'utf8')
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||||
const banned = [...surfaces.SUPPORTED_SKILL_PAGES, ...reg4.list()];
|
||||
const hits = banned.filter((w) => new RegExp(`['"\`]${w}['"\`]`).test(code));
|
||||
@@ -9518,7 +9518,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
|
||||
record('the editor never evaluates a string as code', (() => {
|
||||
const files = ['UiEditor.jsx', 'TreePanel.jsx', 'NodeInspector.jsx', 'NodePicker.jsx', 'ops.js']
|
||||
.map((f) => readFileSync(join(ROOT, 'src/components/ui-editor', f), 'utf8'))
|
||||
.map((f) => readFileSync(join(ROOT, resolveSourcePath(`src/components/ui-editor/${f}`)), 'utf8'))
|
||||
.join('\n')
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||||
return !/\beval\s*\(/.test(files) && !/new\s+Function/.test(files)
|
||||
@@ -9527,7 +9527,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
|
||||
record('the editor contains no page name and no component branch', (() => {
|
||||
const files = ['UiEditor.jsx', 'TreePanel.jsx', 'NodeInspector.jsx', 'NodePicker.jsx', 'ops.js']
|
||||
.map((f) => readFileSync(join(ROOT, 'src/components/ui-editor', f), 'utf8'))
|
||||
.map((f) => readFileSync(join(ROOT, resolveSourcePath(`src/components/ui-editor/${f}`)), 'utf8'))
|
||||
.join('\n')
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||||
const banned = [...surfaces.SUPPORTED_SKILL_PAGES, ...nreg.list()];
|
||||
@@ -9740,7 +9740,7 @@ console.log('\n── Candidates vs Talent Pool ──');
|
||||
* `scripts/browser-flows.js`, and only held to its contract here.
|
||||
*/
|
||||
record('every node is rendered inside a boundary', (() => {
|
||||
const source = readFileSync(join(ROOT, 'src/components/ui-tree/UiTreeRenderer.jsx'), 'utf8');
|
||||
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/ui-tree/UiTreeRenderer.jsx')), 'utf8');
|
||||
return {
|
||||
pass: /<UiNodeBoundary node=\{node\}>/.test(source)
|
||||
&& /import \{ UiNodeBoundary \}/.test(source),
|
||||
|
||||
Reference in New Issue
Block a user