@thomas.f/prnext 0.1.16-alpha → 0.1.17-alpha
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.
- package/AI.md +63 -0
- package/README.md +384 -9
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +5 -0
- package/build/analyze.mjs +184 -0
- package/build/app-config.mjs +7 -8
- package/build/app-static.mjs +12 -7
- package/build/app.mjs +23 -10
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +20 -5
- package/build/check.mjs +56 -11
- package/build/compiler.mjs +25 -1
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +21 -13
- package/build/css-modules.mjs +15 -4
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +83 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +55 -4
- package/build/edge.mjs +3 -2
- package/build/framework-imports.mjs +40 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +96 -101
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +2 -3
- package/build/migrate.mjs +60 -57
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +97 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-pages.mjs +90 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +80 -0
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +15 -3
- package/build/transform-cache.mjs +37 -4
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +91 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +22 -0
- package/build/typescript-watch.mjs +22 -0
- package/build/typescript.mjs +5 -4
- package/build/webpack-graph.mjs +164 -27
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +105 -121
- package/compat/index.d.ts +21 -7
- package/compat/link.d.cts +4 -3
- package/compat/navigation.d.cts +4 -3
- package/compat/router.d.cts +4 -3
- package/compat/typed-route.d.cts +17 -0
- package/docs/ai/compatibility.md +106 -0
- package/docs/ai/data.md +128 -0
- package/docs/ai/deployment.md +111 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +146 -0
- package/docs/ai/routing.md +132 -0
- package/docs/ai/troubleshooting.md +120 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +25 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +17 -6
- package/runtime/app-render.mjs +12 -0
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +239 -45
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +186 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +18 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +307 -0
- package/runtime/env.mjs +2 -1
- package/runtime/pages-client.mjs +10 -1
- package/runtime/pages-navigation.mjs +6 -1
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/preload.mjs +12 -0
- package/runtime/rsc-worker.mjs +7 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +12 -5
- package/runtime/worker.mjs +36 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { packageManifest, nativePlatform, resolveNativeBinary } from './resolve.mjs';
|
|
2
|
+
|
|
3
|
+
// The package's Node engines use explicit minimum versions and major ranges.
|
|
4
|
+
// Keep this check local: a diagnostic must never fetch package metadata.
|
|
5
|
+
export function supportedNode(version, range = packageManifest.engines.node) {
|
|
6
|
+
const parsed = /^(?:v)?(\d+)\.(\d+)\.(\d+)(?:\+[^\s]+)?$/.exec(version);
|
|
7
|
+
if (!parsed) return false;
|
|
8
|
+
const actual = parsed.slice(1).map(Number);
|
|
9
|
+
return range.split('||').some(part => {
|
|
10
|
+
const match = /^\s*(\^|>=)(\d+)\.(\d+)\.(\d+)\s*$/.exec(part);
|
|
11
|
+
if (!match) return false;
|
|
12
|
+
const minimum = match.slice(2).map(Number);
|
|
13
|
+
const compared = actual[0] - minimum[0] || actual[1] - minimum[1] || actual[2] - minimum[2];
|
|
14
|
+
return compared >= 0 && (match[1] !== '^' || actual[0] === minimum[0]);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Inspect installation metadata and executable access; never execute or build it. */
|
|
19
|
+
export async function environmentDiagnostics({ version = process.version, platform = process.platform, arch = process.arch,
|
|
20
|
+
libc, env = process.env, resolve = resolveNativeBinary } = {}) {
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
diagnostics.push(supportedNode(version)
|
|
23
|
+
? { id: 'node', severity: 'info', message: `Node ${version} satisfies ${packageManifest.engines.node}.` }
|
|
24
|
+
: { id: 'node', severity: 'error', message: `Node ${version} is unsupported.`, action: `Use Node ${packageManifest.engines.node}.` });
|
|
25
|
+
try {
|
|
26
|
+
const target = env.PRNEXT_BINARY ? null : nativePlatform({ platform, arch, libc });
|
|
27
|
+
await resolve({ env, allowBuild: false, platform: { platform, arch, libc } });
|
|
28
|
+
diagnostics.push({ id: 'native', severity: 'info', message: `Native executable is accessible for ${platform}/${arch}${target?.libc ? `/${target.libc}` : ''}; metadata/access checked without running it.`, ...(target ? { package: target.package } : { override: true }) });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
diagnostics.push({ id: 'native', severity: 'error', message: error.message,
|
|
31
|
+
action: 'Install matching PRNext optional dependencies on this machine. For a source checkout, build the native server explicitly; check never builds it.' });
|
|
32
|
+
}
|
|
33
|
+
return diagnostics;
|
|
34
|
+
}
|
package/native/resolve.mjs
CHANGED
|
@@ -14,10 +14,15 @@ export const nativeBinaryName = (platform = process.platform) => platform === 'w
|
|
|
14
14
|
// the framework checkout, even if that consumer also has a Cargo workspace.
|
|
15
15
|
export function sourceCheckout(root = packageRoot) {
|
|
16
16
|
try {
|
|
17
|
-
|
|
17
|
+
// Package-manager portals and linked checkouts may expose a virtual path.
|
|
18
|
+
// Establish the physical package root before looking for its repository.
|
|
19
|
+
// The native resolver also expands Windows DOS aliases and canonicalizes
|
|
20
|
+
// junction target casing; the JS resolver preserves their stored spelling.
|
|
21
|
+
const canonicalRoot = realpathSync.native(root);
|
|
22
|
+
const repository = path.resolve(canonicalRoot, '../..');
|
|
18
23
|
const manifest = JSON.parse(readFileSync(path.join(repository, 'package.json'), 'utf8'));
|
|
19
24
|
if (manifest.name !== 'prnext-monorepo' || manifest.private !== true) return null;
|
|
20
|
-
if (
|
|
25
|
+
if (canonicalRoot !== realpathSync.native(path.join(repository, 'packages/prnext'))) return null;
|
|
21
26
|
readFileSync(path.join(repository, 'crates/prnext/Cargo.toml'));
|
|
22
27
|
return repository;
|
|
23
28
|
} catch { return null; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thomas.f/prnext",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17-alpha",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Experimental Rust-powered runtime for Next.js apps, React and npm",
|
|
6
6
|
"homepage": "https://prnext.dev",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"registry": "https://registry.npmjs.org/"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
|
+
"AI.md",
|
|
37
|
+
"docs/ai/",
|
|
36
38
|
"cli.mjs",
|
|
37
39
|
"index.mjs",
|
|
38
40
|
"build/",
|
|
@@ -49,6 +51,14 @@
|
|
|
49
51
|
"prn": "./cli.mjs"
|
|
50
52
|
},
|
|
51
53
|
"exports": {
|
|
54
|
+
"./AI.md": "./AI.md",
|
|
55
|
+
"./docs/ai/manifest.json": "./docs/ai/manifest.json",
|
|
56
|
+
"./docs/ai/quickstart.md": "./docs/ai/quickstart.md",
|
|
57
|
+
"./docs/ai/routing.md": "./docs/ai/routing.md",
|
|
58
|
+
"./docs/ai/data.md": "./docs/ai/data.md",
|
|
59
|
+
"./docs/ai/compatibility.md": "./docs/ai/compatibility.md",
|
|
60
|
+
"./docs/ai/deployment.md": "./docs/ai/deployment.md",
|
|
61
|
+
"./docs/ai/troubleshooting.md": "./docs/ai/troubleshooting.md",
|
|
52
62
|
"./package.json": "./package.json",
|
|
53
63
|
"./og": {
|
|
54
64
|
"types": "./compat/og.d.cts",
|
|
@@ -66,6 +76,10 @@
|
|
|
66
76
|
"types": "./compat/index.d.ts",
|
|
67
77
|
"default": "./index.mjs"
|
|
68
78
|
},
|
|
79
|
+
"./route": {
|
|
80
|
+
"types": "./compat/typed-route.d.cts",
|
|
81
|
+
"default": "./runtime/typed-route.mjs"
|
|
82
|
+
},
|
|
69
83
|
"./link": "./compat/link.cjs",
|
|
70
84
|
"./head": "./compat/head.cjs",
|
|
71
85
|
"./script": {
|
|
@@ -111,11 +125,15 @@
|
|
|
111
125
|
"@babel/types": "^7.29.0",
|
|
112
126
|
"@edge-runtime/vm": "5.0.0",
|
|
113
127
|
"@vercel/nft": "1.11.0",
|
|
128
|
+
"browserslist": "4.29.0",
|
|
114
129
|
"dotenv": "16.6.1",
|
|
115
130
|
"dotenv-expand": "11.0.7",
|
|
116
131
|
"esbuild": "^0.28.2",
|
|
117
132
|
"fontkit": "2.0.4",
|
|
133
|
+
"html-to-image": "1.11.13",
|
|
134
|
+
"lightningcss": "1.31.0",
|
|
118
135
|
"loader-runner": "4.3.2",
|
|
136
|
+
"oxc-parser": "0.155.0",
|
|
119
137
|
"path-to-regexp": "6.3.0",
|
|
120
138
|
"picomatch": "4.0.7",
|
|
121
139
|
"postcss": "8.5.28",
|
|
@@ -132,12 +150,12 @@
|
|
|
132
150
|
"react-server-dom-webpack": "19.3.1"
|
|
133
151
|
},
|
|
134
152
|
"optionalDependencies": {
|
|
135
|
-
"prnext-
|
|
136
|
-
"prnext-
|
|
137
|
-
"prnext-
|
|
138
|
-
"prnext-
|
|
139
|
-
"
|
|
140
|
-
"
|
|
153
|
+
"@thomas.f/prnext-win32-arm64-msvc": "0.1.17-alpha",
|
|
154
|
+
"@thomas.f/prnext-win32-x64-msvc": "0.1.17-alpha",
|
|
155
|
+
"prnext-darwin-arm64": "0.1.17-alpha",
|
|
156
|
+
"prnext-darwin-x64": "0.1.17-alpha",
|
|
157
|
+
"prnext-linux-arm64-gnu": "0.1.17-alpha",
|
|
158
|
+
"prnext-linux-x64-gnu": "0.1.17-alpha"
|
|
141
159
|
},
|
|
142
160
|
"engines": {
|
|
143
161
|
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile, access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const expectedTopics = ['quickstart', 'routing', 'data', 'compatibility', 'deployment', 'troubleshooting'];
|
|
5
|
+
|
|
6
|
+
// These guides ship as package API. Validate their distribution and local links
|
|
7
|
+
// on every release, independently of a website or a native server installation.
|
|
8
|
+
export async function verifyAiDocumentation(root, pkg) {
|
|
9
|
+
if (!pkg.files?.includes('AI.md') || !pkg.files?.includes('docs/ai/')) {
|
|
10
|
+
throw new Error('The npm files list must include AI.md and docs/ai/.');
|
|
11
|
+
}
|
|
12
|
+
const manifest = JSON.parse(await readFile(path.join(root, 'docs/ai/manifest.json'), 'utf8'));
|
|
13
|
+
if (manifest.schemaVersion !== 1 || !Array.isArray(manifest.topics) ||
|
|
14
|
+
JSON.stringify(manifest.topics.map(topic => topic.id)) !== JSON.stringify(expectedTopics)) {
|
|
15
|
+
throw new Error('AI documentation manifest has missing, duplicate or unexpected topics.');
|
|
16
|
+
}
|
|
17
|
+
const files = ['AI.md'];
|
|
18
|
+
for (const topic of manifest.topics) {
|
|
19
|
+
if (topic.file !== `docs/ai/${topic.id}.md` || typeof topic.title !== 'string' || !topic.title.trim() || /[\r\n\x00-\x1f]/.test(topic.title)) {
|
|
20
|
+
throw new Error(`Invalid AI documentation topic: ${topic.id}`);
|
|
21
|
+
}
|
|
22
|
+
files.push(topic.file);
|
|
23
|
+
}
|
|
24
|
+
for (const file of [...files, 'docs/ai/manifest.json']) {
|
|
25
|
+
if (pkg.exports?.[`./${file}`] !== `./${file}`) throw new Error(`Missing npm documentation export: ${file}`);
|
|
26
|
+
}
|
|
27
|
+
let totalBytes = 0;
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const content = await readFile(path.join(root, file), 'utf8');
|
|
30
|
+
const bytes = Buffer.byteLength(content);
|
|
31
|
+
totalBytes += bytes;
|
|
32
|
+
if (!/^# .+/m.test(content) || bytes > (file === 'AI.md' ? 6_000 : 32_000)) {
|
|
33
|
+
throw new Error(`AI guide is missing its heading or exceeds its focused reading budget: ${file}`);
|
|
34
|
+
}
|
|
35
|
+
for (const match of content.matchAll(/\[[^\]\n]+\]\(([^)\s]+)\)/g)) {
|
|
36
|
+
const link = match[1];
|
|
37
|
+
if (link.startsWith('#') || /^https?:\/\//.test(link)) continue;
|
|
38
|
+
const target = path.resolve(root, path.dirname(file), decodeURIComponent(link.split('#')[0]));
|
|
39
|
+
const relative = path.relative(root, target);
|
|
40
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error(`AI guide link leaves the package: ${file}: ${link}`);
|
|
41
|
+
try { await access(target); }
|
|
42
|
+
catch { throw new Error(`Broken AI guide link: ${file}: ${link}`); }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (totalBytes > 100_000) throw new Error('AI documentation exceeds the total focused reading budget.');
|
|
46
|
+
await access(path.join(root, 'runtime/ai-docs.mjs'));
|
|
47
|
+
return { topics: manifest.topics.length, files: files.length, bytes: totalBytes };
|
|
48
|
+
}
|
package/release/verify.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile, access } from 'node:fs/promises';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { packageRoot, packageManifest, platforms, sourceCheckout } from '../native/resolve.mjs';
|
|
5
|
+
import { verifyAiDocumentation } from './ai-docs.mjs';
|
|
5
6
|
|
|
6
7
|
export async function verifyPackage() {
|
|
7
8
|
const pkg = packageManifest;
|
|
@@ -25,6 +26,7 @@ export async function verifyPackage() {
|
|
|
25
26
|
for (const target of Object.values(value)) await exported(target);
|
|
26
27
|
}
|
|
27
28
|
await exported(pkg.exports);
|
|
29
|
+
await verifyAiDocumentation(packageRoot, pkg);
|
|
28
30
|
await access(path.join(packageRoot, 'runtime/profiles.json'));
|
|
29
31
|
for (const name of ['index.node.mjs', 'resvg.wasm', 'hb.wasm', 'Geist-Regular.ttf', 'LICENSE', 'LICENSE.fflate', 'LICENSE.Geist', 'LICENSE.harfbuzzjs', 'LICENSE.satori', 'NOTICE.txt']) await access(path.join(packageRoot, 'runtime/og', name));
|
|
30
32
|
if (pkg.dependencies['@vercel/og'] || (await readFile(path.join(packageRoot, 'runtime/og/index.node.mjs'), 'utf8')).includes('fflate@0.7.3')) throw new Error('OG renderer still depends on vulnerable upstream fflate. Run scripts/prepare-og.mjs.');
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import fs, {constants} from 'node:fs';
|
|
2
|
+
import {lstat, open, realpath, unlink} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
|
|
6
|
+
const defaultPackageRoot = fileURLToPath(new URL('../', import.meta.url));
|
|
7
|
+
const maxDocumentBytes = 256 * 1024;
|
|
8
|
+
const maxInstructionsBytes = 1024 * 1024;
|
|
9
|
+
const startMarker = '<!-- prnext-ai-docs:start -->';
|
|
10
|
+
const endMarker = '<!-- prnext-ai-docs:end -->';
|
|
11
|
+
const noFollow = constants.O_NOFOLLOW || 0;
|
|
12
|
+
|
|
13
|
+
const help = `Usage: prnext docs --ai [topic] [--json]
|
|
14
|
+
prnext docs --ai --list [--json]
|
|
15
|
+
prnext docs --ai --init [directory] [--json]
|
|
16
|
+
|
|
17
|
+
Read the AI documentation shipped with the installed PRNext package.
|
|
18
|
+
With no topic, print the short documentation index. --list shows available topics.
|
|
19
|
+
--init explicitly appends a short documentation pointer to a project's AGENTS.md.
|
|
20
|
+
Existing instructions are preserved; no files are changed by documentation reads.
|
|
21
|
+
For initialization, use a physical directory path without symbolic links or junctions.
|
|
22
|
+
No native binary, project configuration, network access, or installation is needed.
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
function parseArguments(args) {
|
|
26
|
+
if (!Array.isArray(args) || args.some(arg => typeof arg !== 'string')) throw new Error('Documentation arguments must be strings.');
|
|
27
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
28
|
+
if (args.every(arg => ['--ai', '--help', '-h'].includes(arg))) return {help: true};
|
|
29
|
+
throw new Error('Use prnext docs --help without other documentation options.');
|
|
30
|
+
}
|
|
31
|
+
const flags = new Set(), positional = [];
|
|
32
|
+
for (const arg of args) {
|
|
33
|
+
if (['--ai', '--list', '--json', '--init'].includes(arg)) {
|
|
34
|
+
if (flags.has(arg)) throw new Error(`Duplicate documentation option: ${arg}`);
|
|
35
|
+
flags.add(arg);
|
|
36
|
+
} else if (arg.startsWith('-')) throw new Error(`Unknown documentation option: ${arg}`);
|
|
37
|
+
else positional.push(arg);
|
|
38
|
+
}
|
|
39
|
+
if (!flags.has('--ai')) throw new Error('Use prnext docs --ai to read the installed AI documentation.');
|
|
40
|
+
if (positional.length > 1) throw new Error('Provide only one documentation topic or initialization directory.');
|
|
41
|
+
if (flags.has('--list') && (flags.has('--init') || positional.length)) throw new Error('--list cannot be combined with a topic or --init.');
|
|
42
|
+
return {list: flags.has('--list'), json: flags.has('--json'), init: flags.has('--init'), value: positional[0]};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function readBounded(handle, limit, label) {
|
|
46
|
+
const info = await handle.stat();
|
|
47
|
+
if (!info.isFile()) throw new Error(`${label} must be a regular file.`);
|
|
48
|
+
if (info.size > limit) throw new Error(`${label} exceeds the ${limit}-byte limit.`);
|
|
49
|
+
const buffer = Buffer.alloc(limit + 1);
|
|
50
|
+
let size = 0;
|
|
51
|
+
while (size < buffer.length) {
|
|
52
|
+
const {bytesRead} = await handle.read(buffer, size, buffer.length - size, size);
|
|
53
|
+
if (!bytesRead) break;
|
|
54
|
+
size += bytesRead;
|
|
55
|
+
}
|
|
56
|
+
if (size > limit) throw new Error(`${label} exceeds the ${limit}-byte limit.`);
|
|
57
|
+
return buffer.subarray(0, size);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function decodeUtf8(buffer, label) {
|
|
61
|
+
try { return new TextDecoder('utf-8', {fatal: true, ignoreBOM: true}).decode(buffer); }
|
|
62
|
+
catch { throw new Error(`${label} must contain UTF-8 text.`); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function readPackageFile(root, relative, limit = maxDocumentBytes) {
|
|
66
|
+
const file = path.resolve(root, relative);
|
|
67
|
+
const canonicalRoot = await realpath(root), canonicalFile = await realpath(file);
|
|
68
|
+
const inside = path.relative(canonicalRoot, canonicalFile);
|
|
69
|
+
if (inside.startsWith(`..${path.sep}`) || inside === '..' || path.isAbsolute(inside)) throw new Error(`Documentation file escapes the installed package: ${relative}`);
|
|
70
|
+
const handle = await open(file, constants.O_RDONLY);
|
|
71
|
+
try { return decodeUtf8(await readBounded(handle, limit, relative), relative); }
|
|
72
|
+
finally { await handle.close(); }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function readCatalog(root) {
|
|
76
|
+
const metadata = JSON.parse(await readPackageFile(root, 'package.json', 64 * 1024));
|
|
77
|
+
if (typeof metadata.name !== 'string' || typeof metadata.version !== 'string') throw new Error('The installed package metadata has no name or version.');
|
|
78
|
+
const manifest = JSON.parse(await readPackageFile(root, 'docs/ai/manifest.json', 64 * 1024));
|
|
79
|
+
if (manifest.schemaVersion !== 1 || !Array.isArray(manifest.topics) || manifest.topics.length > 64) throw new Error('The AI documentation manifest must use schemaVersion 1 and contain a bounded topics array.');
|
|
80
|
+
const topics = [{id: 'index', title: 'AI documentation index', file: 'AI.md'}], ids = new Set(['index']);
|
|
81
|
+
for (const topic of manifest.topics) {
|
|
82
|
+
if (!topic || !/^[a-z][a-z0-9-]*$/.test(topic.id) || ids.has(topic.id)
|
|
83
|
+
|| typeof topic.title !== 'string' || !topic.title.trim() || /[\r\n\x00-\x1f]/.test(topic.title)
|
|
84
|
+
|| topic.file !== `docs/ai/${topic.id}.md`) throw new Error('The AI documentation manifest contains an invalid or duplicate topic.');
|
|
85
|
+
ids.add(topic.id);
|
|
86
|
+
topics.push({id: topic.id, title: topic.title, file: topic.file});
|
|
87
|
+
}
|
|
88
|
+
return {name: metadata.name, version: metadata.version, topics};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function instructionBlock(newline) {
|
|
92
|
+
return [startMarker, '## PRNext documentation', '',
|
|
93
|
+
'Before changing PRNext configuration, routes, data fetching, or deployment, read the documentation shipped with the installed package:', '',
|
|
94
|
+
'`npx --no-install prnext docs --ai`', '',
|
|
95
|
+
'Use `npx --no-install prnext docs --ai --list` to find topics, then read only the relevant topic with `npx --no-install prnext docs --ai <topic>`.',
|
|
96
|
+
'With pnpm or Yarn, use `pnpm exec prnext docs --ai` or `yarn prnext docs --ai` instead. The installed package documentation is the version-specific reference; do not assume full Next.js compatibility.',
|
|
97
|
+
endMarker, ''].join(newline);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function samePath(left, right) {
|
|
101
|
+
return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function checkDirectory(directory) {
|
|
105
|
+
const parts = path.relative(path.parse(directory).root, directory).split(path.sep).filter(Boolean);
|
|
106
|
+
let current = path.parse(directory).root;
|
|
107
|
+
for (const part of parts) {
|
|
108
|
+
current = path.join(current, part);
|
|
109
|
+
const info = await lstat(current);
|
|
110
|
+
if (info.isSymbolicLink()) throw new Error(`Refusing to initialize through a symbolic link or junction: ${current}`);
|
|
111
|
+
if (!info.isDirectory()) throw new Error(`Initialization directory is not a directory: ${current}`);
|
|
112
|
+
}
|
|
113
|
+
if (!samePath(await realpath(directory), directory)) throw new Error('Initialization directory resolves outside its requested path.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function sameFile(left, right) {
|
|
117
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function verifyTarget(directory, file, handle) {
|
|
121
|
+
await checkDirectory(directory);
|
|
122
|
+
const actual = await lstat(file), opened = await handle.stat();
|
|
123
|
+
if (actual.isSymbolicLink() || !actual.isFile() || !sameFile(actual, opened) || opened.nlink !== 1) {
|
|
124
|
+
throw new Error('AGENTS.md changed identity or is linked; no instructions were appended.');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function hasPointer(content) {
|
|
129
|
+
const starts = content.split(startMarker).length - 1, ends = content.split(endMarker).length - 1;
|
|
130
|
+
if (!starts && !ends) return false;
|
|
131
|
+
if (starts !== 1 || ends !== 1 || content.indexOf(startMarker) > content.indexOf(endMarker)) {
|
|
132
|
+
throw new Error('AGENTS.md contains incomplete or duplicate PRNext documentation markers; preserve and fix those markers before initializing again.');
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function initializeInstructions(directory) {
|
|
138
|
+
await checkDirectory(directory);
|
|
139
|
+
const file = path.join(directory, 'AGENTS.md'), lockFile = path.join(directory, '.prnext-ai-docs.lock');
|
|
140
|
+
let lock;
|
|
141
|
+
try { lock = await open(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow, 0o600); }
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error.code === 'EEXIST') throw new Error(`Another initialization or an existing lock prevents changes: ${lockFile}`);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
let handle;
|
|
147
|
+
try {
|
|
148
|
+
await checkDirectory(directory);
|
|
149
|
+
let existing;
|
|
150
|
+
try { existing = await lstat(file); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
151
|
+
if (existing && (existing.isSymbolicLink() || !existing.isFile() || existing.nlink !== 1)) throw new Error('AGENTS.md must be a regular file with no symbolic or hard links.');
|
|
152
|
+
handle = await open(file, existing
|
|
153
|
+
? constants.O_RDWR | constants.O_APPEND | noFollow
|
|
154
|
+
: constants.O_RDWR | constants.O_APPEND | constants.O_CREAT | constants.O_EXCL | noFollow, 0o644);
|
|
155
|
+
await verifyTarget(directory, file, handle);
|
|
156
|
+
const before = await readBounded(handle, maxInstructionsBytes, 'AGENTS.md');
|
|
157
|
+
const content = decodeUtf8(before, 'AGENTS.md');
|
|
158
|
+
if (hasPointer(content)) return {path: file, changed: false};
|
|
159
|
+
const newline = content.includes('\r\n') ? '\r\n' : '\n';
|
|
160
|
+
const separator = !content ? '' : content.endsWith(newline + newline) ? '' : content.endsWith(newline) ? newline : newline + newline;
|
|
161
|
+
const addition = Buffer.from(separator + instructionBlock(newline));
|
|
162
|
+
if (before.length + addition.length > maxInstructionsBytes) throw new Error('Appending the PRNext pointer would exceed the AGENTS.md size limit.');
|
|
163
|
+
// Append through the verified handle: concurrent appends never get replaced by
|
|
164
|
+
// a stale full-file write. Refuse observed edits or target replacement.
|
|
165
|
+
if (!before.equals(await readBounded(handle, maxInstructionsBytes, 'AGENTS.md'))) throw new Error('AGENTS.md changed during initialization; retry without overwriting those edits.');
|
|
166
|
+
await verifyTarget(directory, file, handle);
|
|
167
|
+
await handle.writeFile(addition);
|
|
168
|
+
// Yarn PnP wraps FileHandle.sync() with an unimplemented method, even for
|
|
169
|
+
// physical project files. Its descriptor still supports callback fsync.
|
|
170
|
+
await new Promise((resolve, reject) => fs.fsync(handle.fd, error => error ? reject(error) : resolve()));
|
|
171
|
+
return {path: file, changed: true};
|
|
172
|
+
} finally {
|
|
173
|
+
await handle?.close();
|
|
174
|
+
// Only remove the lock we created, never a replacement supplied by another process.
|
|
175
|
+
try {
|
|
176
|
+
const opened = await lock.stat(), actual = await lstat(lockFile);
|
|
177
|
+
if (sameFile(opened, actual) && !actual.isSymbolicLink()) await unlink(lockFile);
|
|
178
|
+
} catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
179
|
+
finally { await lock.close(); }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Read bundled docs or explicitly append a project pointer. Never launches native code. */
|
|
184
|
+
export async function executeAiDocs(args, {cwd = process.cwd(), packageRoot = defaultPackageRoot} = {}) {
|
|
185
|
+
const options = parseArguments(args);
|
|
186
|
+
if (options.help) return {output: help};
|
|
187
|
+
const catalog = await readCatalog(packageRoot);
|
|
188
|
+
if (options.init) {
|
|
189
|
+
const initialized = await initializeInstructions(path.resolve(cwd, options.value || '.'));
|
|
190
|
+
const result = {name: catalog.name, version: catalog.version, initialized};
|
|
191
|
+
return {initialized, output: options.json ? JSON.stringify(result, null, 2) + '\n'
|
|
192
|
+
: `${initialized.changed ? 'Added the PRNext documentation pointer to' : 'Kept the existing PRNext documentation pointer in'} ${initialized.path}\n`};
|
|
193
|
+
}
|
|
194
|
+
if (options.list) return {output: options.json ? JSON.stringify(catalog, null, 2) + '\n'
|
|
195
|
+
: `${catalog.name} ${catalog.version}\n\n${catalog.topics.map(topic => `${topic.id.padEnd(18)} ${topic.title}`).join('\n')}\n`};
|
|
196
|
+
const id = options.value || 'index';
|
|
197
|
+
const topic = catalog.topics.find(item => item.id === id);
|
|
198
|
+
if (!topic) throw new Error(`Unknown AI documentation topic: ${id}. Use prnext docs --ai --list.`);
|
|
199
|
+
const content = await readPackageFile(packageRoot, topic.file);
|
|
200
|
+
return {output: options.json ? JSON.stringify({...catalog, topic: topic.id, content}, null, 2) + '\n'
|
|
201
|
+
: `${catalog.name} ${catalog.version} — ${topic.title}\n\n${content}${content.endsWith('\n') ? '' : '\n'}`};
|
|
202
|
+
}
|
package/runtime/app-client.mjs
CHANGED
|
@@ -242,7 +242,8 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
async function requestFlight(url, signal, routing = currentRouting, onPartial) {
|
|
245
|
+
async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false}={}) {
|
|
246
|
+
if(process.env.NODE_ENV==='development'&&!routing?.refresh&&!skipEnsure)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(url.href);
|
|
246
247
|
const exported = window.__PRNEXT_STATIC_EXPORT__ === true;
|
|
247
248
|
const target = exported ? new URL(basePath + '/_prnext/flight' + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
|
|
248
249
|
const response = await fetch(target.href, {
|
|
@@ -444,7 +445,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
444
445
|
} else {
|
|
445
446
|
const abort = new AbortController();
|
|
446
447
|
activeRequest = abort;
|
|
447
|
-
|
|
448
|
+
// A refresh is already inside the compiler's apply transaction. Calling
|
|
449
|
+
// ensureRoute from here can wait for that same transaction forever when
|
|
450
|
+
// this tree has no routing state (the ordinary single-route case).
|
|
451
|
+
result = await requestFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting, undefined, {skipEnsure:refresh});
|
|
448
452
|
}
|
|
449
453
|
if (token !== navigation) return;
|
|
450
454
|
if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
|
|
@@ -459,7 +463,8 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
459
463
|
const committed = () => {
|
|
460
464
|
if (token === navigation && scroll && !refresh) scrollToTarget(result.url, savedScroll);
|
|
461
465
|
};
|
|
462
|
-
startTransition(()
|
|
466
|
+
if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>startTransition(()=>root.render(appView({model:result.model,controller,committed:()=>{committed();resolve();},url:result.url.href,globalErrorComponent,globalErrorCss}))));
|
|
467
|
+
else startTransition(() => root.render(appView({ model: result.model, controller, committed, url: result.url.href, globalErrorComponent, globalErrorCss })));
|
|
463
468
|
} catch (error) {
|
|
464
469
|
if (token !== navigation || error?.name === 'AbortError') return;
|
|
465
470
|
// Pages Router routes, public files, and unavailable Flight endpoints use
|
|
@@ -496,7 +501,9 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
496
501
|
},
|
|
497
502
|
};
|
|
498
503
|
|
|
499
|
-
|
|
504
|
+
let resolveDevReady;
|
|
505
|
+
const devReady=process.env.NODE_ENV==='development'?new Promise(resolve=>{resolveDevReady=resolve;}):undefined;
|
|
506
|
+
const initialView = appView({ model: initialModel, controller, globalErrorComponent, globalErrorCss, ...(devReady?{committed:resolveDevReady}:{}) });
|
|
500
507
|
const rootOptions = {
|
|
501
508
|
formState: initialModel.formState ?? null,
|
|
502
509
|
onCaughtError(error) {
|
|
@@ -506,7 +513,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
506
513
|
}
|
|
507
514
|
},
|
|
508
515
|
onRecoverableError(error) {
|
|
509
|
-
if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause))
|
|
516
|
+
if (!getNavigationControl(error) && !isDynamicBailout(error) && !isDynamicBailout(error?.cause)) {
|
|
517
|
+
console.error(error);
|
|
518
|
+
if (process.env.NODE_ENV === 'development') globalThis.__PRNEXT_DEV__?.reportRecoverableError(error);
|
|
519
|
+
}
|
|
510
520
|
},
|
|
511
521
|
};
|
|
512
522
|
if (document.documentElement.id === '__prnext_error__') {
|
|
@@ -525,8 +535,9 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
|
|
|
525
535
|
scrollFrame = requestAnimationFrame(() => { scrollFrame = undefined; saveScroll(); });
|
|
526
536
|
}, { passive: true });
|
|
527
537
|
return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
|
|
538
|
+
devReady,
|
|
528
539
|
devPrepare: options => moduleLoader.updateModules(options.clientModules),
|
|
529
|
-
devCommit: () =>
|
|
540
|
+
devCommit: () => {invalidatePrefetches();return navigate(new URL(window.location.href),{refresh:true});},
|
|
530
541
|
} : {}) };
|
|
531
542
|
}
|
|
532
543
|
|
package/runtime/app-render.mjs
CHANGED
|
@@ -233,6 +233,18 @@ function getWorker(production) {
|
|
|
233
233
|
current.unref();
|
|
234
234
|
return current;
|
|
235
235
|
}
|
|
236
|
+
export async function preloadAppRoutes(files,options){
|
|
237
|
+
const worker=getWorker(true),id=`preload-${++nextId}`;
|
|
238
|
+
worker.ref();
|
|
239
|
+
try{await new Promise((resolve,reject)=>{
|
|
240
|
+
const clean=()=>{clearTimeout(timer);worker.off('message',message);worker.off('error',error);worker.off('exit',exit);};
|
|
241
|
+
const message=value=>{if(value.type==='preloaded'&&value.id===id){clean();value.error?reject(new Error(value.error)):resolve();}};
|
|
242
|
+
const error=value=>{clean();reject(value);};const exit=()=>error(new Error('RSC worker exited while preloading'));
|
|
243
|
+
const timer=setTimeout(()=>{clean();void lifecycles.get(worker)?.terminate(new Error('RSC preload timed out'));reject(new Error('RSC preload timed out'));},options.timeoutMs);
|
|
244
|
+
worker.on('message',message);worker.once('error',error);worker.once('exit',exit);
|
|
245
|
+
worker.postMessage({type:'preload',id,value:{files,options}});
|
|
246
|
+
});}finally{if(!hasPending(worker)&&!lifecycles.get(worker)?.retiring)worker.unref();}
|
|
247
|
+
}
|
|
236
248
|
|
|
237
249
|
// Stdio workers can retain referenced MessagePorts after rendering (including
|
|
238
250
|
// diagnostics piped from the RSC isolate). EOF must drain real cache/after()
|
|
@@ -237,12 +237,10 @@ export async function archiveProject(
|
|
|
237
237
|
throw new Error('package.json is not valid JSON.');
|
|
238
238
|
}
|
|
239
239
|
if (
|
|
240
|
-
!packageJson ||
|
|
241
|
-
typeof packageJson.scripts?.build !== 'string' ||
|
|
242
|
-
!packageJson.scripts.build.trim()
|
|
240
|
+
!packageJson || typeof packageJson !== 'object' || Array.isArray(packageJson)
|
|
243
241
|
)
|
|
244
242
|
throw new Error(
|
|
245
|
-
'
|
|
243
|
+
'package.json must contain an object.'
|
|
246
244
|
);
|
|
247
245
|
}
|
|
248
246
|
let compressed = deflateRawSync(contents, { level: 6 });
|
|
@@ -291,10 +289,11 @@ export async function archiveProject(
|
|
|
291
289
|
}
|
|
292
290
|
}
|
|
293
291
|
await walk();
|
|
294
|
-
if (!packageJson)
|
|
292
|
+
if (!files.includes('index.html') && (typeof packageJson?.scripts?.build !== 'string' || !packageJson.scripts.build.trim()))
|
|
295
293
|
throw new Error(
|
|
296
|
-
'
|
|
294
|
+
'Include index.html for a plain site, or package.json with a production build script; do not exclude it.'
|
|
297
295
|
);
|
|
296
|
+
packageJson ||= {};
|
|
298
297
|
const end = Buffer.alloc(22);
|
|
299
298
|
end.writeUInt32LE(0x06054b50);
|
|
300
299
|
end.writeUInt16LE(files.length, 8);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {flushCompileCache} from 'node:module';
|
|
2
|
+
let pending;
|
|
3
|
+
// Flush after imports settle so later workers can reuse compiled code even
|
|
4
|
+
// while this worker stays warm. Disk failure is an optimization miss only.
|
|
5
|
+
export function scheduleCompileCacheFlush(){
|
|
6
|
+
if(pending||!process.env.NODE_COMPILE_CACHE||process.env.NODE_DISABLE_COMPILE_CACHE==='1')return;
|
|
7
|
+
pending=setTimeout(()=>{pending=undefined;try{flushCompileCache();}catch{}},1000);pending.unref();
|
|
8
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Offline identification marks copied from PRNext Host's local assets:
|
|
2
|
+
// hosting/public/assets/ai-icons/{codex,cursor,claude,other}.svg (vendored 2026-10-20).
|
|
3
|
+
// Provenance retained from that directory's README:
|
|
4
|
+
// - Codex/OpenAI: official Codex VS Code extension 26.1007.21434,
|
|
5
|
+
// resources/blossom-white.svg (OpenAI blossom mark).
|
|
6
|
+
// - Cursor: official https://cursor.com/marketing-static/favicon.svg;
|
|
7
|
+
// brand guidance https://cursor.com/brand.
|
|
8
|
+
// - Claude: official https://claude.com/favicon.svg (also claude.ai/favicon.svg).
|
|
9
|
+
// Brand marks remain their owners' trademarks; no trademark-license or
|
|
10
|
+
// endorsement claim is made. Geometry/colors are preserved. Only decorative
|
|
11
|
+
// accessibility attributes, display size and Cursor's local clip ID are changed.
|
|
12
|
+
// The generic MCP symbol is local, unbranded and inherits the text color.
|
|
13
|
+
// These strings are trusted constants only; never interpolate remote markup.
|
|
14
|
+
|
|
15
|
+
const codexIcon="<svg width=\"20\" height=\"20\" aria-hidden=\"true\" focusable=\"false\"\n xmlns=\"http://www.w3.org/2000/svg\"\n \n \n fill=\"none\"\n viewBox=\"0 0 24 24\"\n>\n <path\n d=\"M13.798 23.976a5.7 5.7 0 0 1-2.26-.456 6.1 6.1 0 0 1-1.903-1.27 5.7 5.7 0 0 1-1.88.311 5.75 5.75 0 0 1-2.95-.79 6.2 6.2 0 0 1-2.188-2.159q-.81-1.366-.809-3.045 0-.695.19-1.51a6.4 6.4 0 0 1-1.475-2.038A5.95 5.95 0 0 1 0 10.573Q0 9.278.547 8.08q.547-1.2 1.523-2.062a5.5 5.5 0 0 1 2.307-1.223A5.7 5.7 0 0 1 5.472 2.35 6.1 6.1 0 0 1 7.565.623 5.8 5.8 0 0 1 10.206 0q1.19 0 2.26.456a6.1 6.1 0 0 1 1.903 1.27 5.7 5.7 0 0 1 1.88-.311q1.594 0 2.95.79a6 6 0 0 1 2.165 2.159q.832 1.366.832 3.045 0 .695-.19 1.51a6.3 6.3 0 0 1 1.475 2.062q.523 1.15.523 2.422a5.9 5.9 0 0 1-.547 2.493q-.547 1.2-1.546 2.086a5.4 5.4 0 0 1-2.284 1.199 5.56 5.56 0 0 1-1.118 2.445 5.9 5.9 0 0 1-2.07 1.727 5.8 5.8 0 0 1-2.64.623m-5.876-2.997q1.19 0 2.07-.504l4.472-2.589a.53.53 0 0 0 .238-.455v-2.062L8.945 18.7a.96.96 0 0 1-1.047 0l-4.496-2.613a.7.7 0 0 1-.024.168v.287q0 1.224.571 2.254a4.24 4.24 0 0 0 1.642 1.583q1.047.6 2.331.599m.238-3.908a.6.6 0 0 0 .262.072q.118 0 .238-.072l1.784-1.031-5.734-3.357q-.522-.312-.523-.935V6.545a4.3 4.3 0 0 0-1.903 1.63 4.25 4.25 0 0 0-.714 2.398q0 1.176.595 2.254.594 1.08 1.546 1.63zm5.638 5.323q1.26 0 2.284-.576a4.3 4.3 0 0 0 1.618-1.582q.595-1.008.595-2.254v-5.179a.47.47 0 0 0-.238-.431l-1.808-1.055v6.689q0 .624-.524.935l-4.496 2.613a4.3 4.3 0 0 0 2.57.84m.904-8.776v-3.26l-2.688-1.535-2.712 1.535v3.26l2.712 1.535zM7.756 5.97q0-.623.523-.935l4.496-2.613a4.3 4.3 0 0 0-2.569-.84q-1.26 0-2.284.576A4.3 4.3 0 0 0 6.304 3.74q-.57 1.008-.57 2.254v5.155q0 .287.237.455l1.785 1.055zM19.84 17.43a4.16 4.16 0 0 0 1.88-1.63 4.33 4.33 0 0 0 .713-2.397q0-1.176-.595-2.254-.594-1.08-1.546-1.63l-4.449-2.59q-.143-.096-.261-.072a.46.46 0 0 0-.238.072L13.56 7.936l5.758 3.38a.9.9 0 0 1 .38.384q.143.216.143.528zM15.059 5.25q.524-.335 1.047 0l4.52 2.662V7.48q0-1.15-.57-2.181A4.14 4.14 0 0 0 18.46 3.62q-1.023-.623-2.379-.623-1.19 0-2.07.503L9.54 6.09a.53.53 0 0 0-.238.455v2.062z\"\n fill=\"white\"\n />\n</svg>";
|
|
16
|
+
const cursorIcon="<svg width=\"20\" height=\"20\" aria-hidden=\"true\" focusable=\"false\" fill=\"none\" viewBox=\"0 0 512 512\" xmlns=\"http://www.w3.org/2000/svg\"><clipPath id=\"prnext-dev-cursor-cutout\"><path d=\"m96 73h320.735v365.65h-320.735z\"/></clipPath><path d=\"m512.001 325.499c0 7.124 0 14.24-.041 21.364-.035 5.999-.103 11.998-.268 17.99-.356 13.068-1.124 26.245-3.448 39.169-2.359 13.109-6.205 25.306-12.266 37.222-5.958 11.703-13.746 22.419-23.029 31.709-9.29 9.291-20 17.072-31.71 23.03-11.909 6.061-24.113 9.907-37.222 12.266-12.923 2.324-26.101 3.092-39.169 3.448-5.999.165-11.991.233-17.99.268-7.123.048-14.24.041-21.364.041h-139c-7.124 0-14.24 0-21.364-.041-5.999-.035-11.998-.103-17.99-.268-13.068-.356-26.245-1.124-39.169-3.448-13.109-2.359-25.306-6.205-37.2219-12.266-11.7034-5.958-22.4195-13.746-31.7095-23.03-9.29-9.29-17.0717-19.999-23.0296-31.709-6.06082-11.909-9.90709-24.113-12.26559-37.222-2.32422-12.924-3.092104-26.101-3.448621-39.169-.164547-5.999-.2331075-11.991-.267388-17.99-.02742444-7.124-.02742444-14.24-.02742444-21.364v-139c0-7.124 0-14.24.04113664-21.364.0342805-5.999.1028418-11.998.2673878-17.99.356517-13.068 1.124399-26.245 3.448619-39.169 2.3585-13.1091 6.20477-25.3061 12.26558-37.222 5.9579-11.7034 13.7465-22.4195 23.0296-31.7095 9.2901-9.29 19.9993-17.0717 31.7095-23.0296 11.9091-6.06082 24.1129-9.90709 37.2222-12.26559 12.923-2.32422 26.101-3.092101 39.168-3.448618 6-.164547 11.992-.2331075 17.991-.2673881 7.117-.03428046 14.233-.03428046 21.357-.03428046h139c7.124 0 14.24 0 21.364.04113656 5.999.0342806 11.998.102842 17.99.267388 13.068.356517 26.245 1.124402 39.169 3.448622 13.109 2.3585 25.306 6.20477 37.222 12.26553 11.703 5.958 22.419 13.7465 31.709 23.0297 9.29 9.29 17.072 19.9992 23.03 31.7094 6.061 11.9091 9.907 24.113 12.266 37.2222 2.324 12.923 3.092 26.101 3.448 39.169.165 5.999.233 11.991.268 17.99.048 7.123.041 14.24.041 21.364v139z\" fill=\"#14120b\"/><path d=\"m186.501 3.99902h139c7.125 0 14.231-.00004 21.341.04102 5.985.0342 11.952.10221 17.903.26562h.001c13.003.35475 25.943 1.11709 38.569 3.3877 12.775 2.29828 24.592 6.03144 36.118 11.89354v-.001c10.973 5.5867 21.052 12.8384 29.848 21.4571l.847.8379c8.993 8.993 16.525 19.3593 22.292 30.6933l3.565-1.8135-3.565 1.8145c5.678 11.1575 9.36 22.6024 11.674 34.9208l.219 1.195c2.129 11.837 2.932 23.95 3.316 36.132l.072 2.438c.164 5.958.232 11.919.266 17.904v.004c.048 7.107.041 14.207.041 21.337v129.344l-.007-.007v9.656c0 7.125 0 14.231-.041 21.341-.034 5.985-.102 11.952-.266 17.903v.001c-.354 13.003-1.117 25.944-3.387 38.569-2.227 12.376-5.8 23.853-11.351 35.037l-.543 1.08c-5.767 11.327-13.306 21.701-22.293 30.695-8.993 8.993-19.361 16.526-30.695 22.293-11.518 5.861-23.342 9.595-36.116 11.894-11.837 2.128-23.95 2.931-36.132 3.315l-2.438.072c-5.958.164-11.919.232-17.904.266h-.004c-7.107.048-14.207.041-21.337.041h-139c-7.125 0-14.231 0-21.341-.041-5.985-.034-11.952-.102-17.903-.266h-.001c-13.003-.355-25.944-1.117-38.57-3.387-12.7742-2.299-24.5914-6.032-36.1165-11.894h.001c-10.974-5.587-21.0534-12.837-29.8496-21.456l-.8467-.838c-8.7118-8.712-16.0531-18.713-21.7461-29.634l-.5459-1.06-.544-1.081c-5.3709-10.816-8.8904-21.919-11.12983-33.84l-.21973-1.196c-2.27061-12.625-3.03294-25.566-3.38769-38.569v-.001c-.16342-5.958-.23143-11.918-.26563-17.903-.02736-7.112-.02734-14.219-.02734-21.341v-139c0-7.125-.00005-14.231.04101-21.341.0342-5.985.10222-11.952.26563-17.903v-.001c.35475-13.003 1.11699-25.944 3.38769-38.57 2.29829-12.7743 6.03159-24.5916 11.89359-36.1166l-.001-.001c5.7665-11.3269 13.3073-21.6999 22.2939-30.6934 8.9933-8.9932 19.36-16.5261 30.6944-22.2929h.0009c11.5174-5.8615 23.3407-9.59617 36.1149-11.89455 11.837-2.12878 23.951-2.93017 36.133-3.31446l2.438-.07226c5.958-.16342 11.919-.23142 17.904-.26563l-.001-.00097c7.104-.03422 14.211-.03321 21.335-.03321z\" stroke=\"#edecec\" stroke-opacity=\".2\" stroke-width=\"8\"/><g clip-path=\"url(#prnext-dev-cursor-cutout)\"><path d=\"m410.344 159.545-146.38-84.5111c-4.7-2.7145-10.5-2.7145-15.2 0l-146.373 84.5111c-3.9515 2.282-6.391 6.501-6.391 11.071v170.418c0 4.569 2.4395 8.789 6.391 11.07l146.379 84.512c4.701 2.714 10.501 2.714 15.201 0l146.38-84.512c3.951-2.281 6.391-6.501 6.391-11.07v-170.418c0-4.57-2.44-8.789-6.391-11.071zm-9.195 17.902-141.308 244.751c-.955 1.65-3.477.976-3.477-.934v-160.261c0-3.203-1.711-6.164-4.487-7.772l-138.786-80.127c-1.65-.956-.976-3.478.934-3.478h282.616c4.013 0 6.522 4.35 4.515 7.828h-.007z\" fill=\"#edecec\"/></g></svg>";
|
|
17
|
+
const claudeIcon="<svg width=\"20\" height=\"20\" aria-hidden=\"true\" focusable=\"false\" viewBox=\"0 0 248 248\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M52.4285 162.873L98.7844 136.879L99.5485 134.602L98.7844 133.334H96.4921L88.7237 132.862L62.2346 132.153L39.3113 131.207L17.0249 130.026L11.4214 128.844L6.2 121.873L6.7094 118.447L11.4214 115.257L18.171 115.847L33.0711 116.911L55.485 118.447L71.6586 119.392L95.728 121.873H99.5485L100.058 120.337L98.7844 119.392L97.7656 118.447L74.5877 102.732L49.4995 86.1905L36.3823 76.62L29.3779 71.7757L25.8121 67.2858L24.2839 57.3608L30.6515 50.2716L39.3113 50.8623L41.4763 51.4531L50.2636 58.1879L68.9842 72.7209L93.4357 90.6804L97.0015 93.6343L98.4374 92.6652L98.6571 91.9801L97.0015 89.2625L83.757 65.2772L69.621 40.8192L63.2534 30.6579L61.5978 24.632C60.9565 22.1032 60.579 20.0111 60.579 17.4246L67.8381 7.49965L71.9133 6.19995L81.7193 7.49965L85.7946 11.0443L91.9074 24.9865L101.714 46.8451L116.996 76.62L121.453 85.4816L123.873 93.6343L124.764 96.1155H126.292V94.6976L127.566 77.9197L129.858 57.3608L132.15 30.8942L132.915 23.4505L136.608 14.4708L143.994 9.62643L149.725 12.344L154.437 19.0788L153.8 23.4505L150.998 41.6463L145.522 70.1215L141.957 89.2625H143.994L146.414 86.7813L156.093 74.0206L172.266 53.698L179.398 45.6635L187.803 36.802L193.152 32.5484H203.34L210.726 43.6549L207.415 55.1159L196.972 68.3492L188.312 79.5739L175.896 96.2095L168.191 109.585L168.882 110.689L170.738 110.53L198.755 104.504L213.91 101.787L231.994 98.7149L240.144 102.496L241.036 106.395L237.852 114.311L218.495 119.037L195.826 123.645L162.07 131.592L161.696 131.893L162.137 132.547L177.36 133.925L183.855 134.279H199.774L229.447 136.524L237.215 141.605L241.8 147.867L241.036 152.711L229.065 158.737L213.019 154.956L175.45 145.977L162.587 142.787H160.805V143.85L171.502 154.366L191.242 172.089L215.82 195.011L217.094 200.682L213.91 205.172L210.599 204.699L188.949 188.394L180.544 181.069L161.696 165.118H160.422V166.772L164.752 173.152L187.803 207.771L188.949 218.405L187.294 221.832L181.308 223.959L174.813 222.777L161.187 203.754L147.305 182.486L136.098 163.345L134.745 164.2L128.075 235.42L125.019 239.082L117.887 241.8L111.902 237.31L108.718 229.984L111.902 215.452L115.722 196.547L118.779 181.541L121.58 162.873L123.291 156.636L123.14 156.219L121.773 156.449L107.699 175.752L86.304 204.699L69.3663 222.777L65.291 224.431L58.2867 220.768L58.9235 214.27L62.8713 208.48L86.304 178.705L100.44 160.155L109.551 149.507L109.462 147.967L108.959 147.924L46.6977 188.512L35.6182 189.93L30.7788 185.44L31.4156 178.115L33.7079 175.752L52.4285 162.873Z\" fill=\"#D97757\"/>\n</svg>";
|
|
18
|
+
const otherIcon="<svg width=\"20\" height=\"20\" aria-hidden=\"true\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m8 7-5 5 5 5m8-10 5 5-5 5m-3-14-2 18\"/></svg>";
|
|
19
|
+
|
|
20
|
+
export const devAiBrands=Object.freeze([
|
|
21
|
+
Object.freeze({id:'codex',label:'Codex',caption:'CLI & VS Code',icon:codexIcon}),
|
|
22
|
+
Object.freeze({id:'cursor',label:'Cursor',caption:'Editor',icon:cursorIcon}),
|
|
23
|
+
Object.freeze({id:'claude',label:'Claude Code',caption:'CLI & editor',icon:claudeIcon}),
|
|
24
|
+
]);
|
|
25
|
+
export const devMcpBrand=Object.freeze({id:'other',label:'Other MCP',caption:'Manual setup',icon:otherIcon});
|
|
26
|
+
export const devAiProviders=Object.freeze([...devAiBrands,devMcpBrand]);
|