Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Repository Instructions

Follow [CLAUDE.md](CLAUDE.md) for repository workflows and requirements.

## Code Style and Readability
- New code must match the surrounding file's formatting and conventions while following the repository's explicit style rules.
- Keep all imports and `require()` declarations at the top of the file (or the top of its AMD module factory). Do not use dynamic or asynchronous imports.
- Write clear, concise JSDoc for new or changed functions, documenting their purpose, parameters, and return values where relevant. Prioritize readability over verbosity; explain non-obvious behavior without repeating the code.

## Tests
- Use the existing Jasmine runner and CI registration. Do not add standalone `node:test` suites or separate test commands unless explicitly requested.
- Before writing tests, inspect a nearby suite, its registration, and the relevant CI workflow. Core specs live in `test/spec/` and are registered in `test/UnitTestSuite.js`; Node-side coverage follows `test/spec/CLILocator-test.js` and `src-node/test/test-cli-locator.js`.
- Follow [CLAUDE.md — Writing Tests](CLAUDE.md#writing-tests) for categories, Node helpers, fixture isolation, and CI coverage. Keep individual cases visible as separate Jasmine `it()` results.
- Verify new suites in the connected `phoenix-test-runner-*` instance using `run_tests` and `get_test_results`. Confirm the expected spec count and category; a passing standalone script or a run with zero specs is not sufficient.
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ Use `exec_js` to run JS in the Phoenix browser runtime. jQuery `$()` is global.
When asked to "run the AI test suite" / "run the model tests" / "run EC-1 and UB-2": call `run_ai_test_suite` (phoenix-builder MCP) with `suite` (`quick` | `all` | a suite name), or `tests` for specific IDs, or `resumeRunId` to continue. It installs the fixture, opens a run record, and returns the briefing plus the test documents from `src/extensionsIntegrated/phoenix-pro/unit-tests/ai_model_tests/`. You are the runner and the judge — follow them exactly, deterministic checks first. After **every** test call `ai_test_progress` and tell the user one progress line. If the user says stop: `ai_test_progress({ runId, stop: true })`, then save. Finish with `save_ai_test_report`, then `compare_ai_test_reports({})`, and tell the user the report path, PASS/FAIL counts, any regressions, and the Observations section.

## Writing Tests
- Use the existing **Jasmine + AMD** framework (`describe`, `it`, `expect`). Do not introduce standalone `node:test` suites or separate runners unless explicitly requested; unregistered tests do not run in CI.
- Before writing a test, inspect a nearby suite, its registration, and the relevant `.github/workflows/` desktop or browser job. Register core specs from `test/spec/` in **`test/UnitTestSuite.js`**. Pro extension specs use `src/extensionsIntegrated/phoenix-pro/unittests.js`; shared `src-node` coverage belongs in the core suite.
- Give new suites an explicit supported category, such as `unit:CLI Locator`. Use `unit` for logic and Node subprocess tests that need no editor iframe or window focus; use `integration` for UI/editor behavior. Desktop Linux, Windows, and macOS CI jobs already run the `unit` category.
- For Node-side behavior, follow `test/spec/CLILocator-test.js` and `src-node/test/test-cli-locator.js`: keep assertions in separate Jasmine `it()` cases and invoke Node helpers with `execPeer()`. Place Node helpers in `src-node/test/` and load them through the existing `src-node/test-connection.js` bootstrap with top-level imports and a dedicated connector ID. Keep helper operations bounded; do not add arbitrary remote-code execution APIs.
- Isolate each test's environment, caches, files, and processes. Use temporary fixtures, bundled `process.execPath` for Node scripts, and `try/finally` cleanup. Do not depend on user-installed CLIs, account credentials, network access, or mutate the app's `process.env`. Use asynchronous child-process APIs so the shared Node event loop remains responsive.
- Gate Node-only suites on `Phoenix.isNativeApp`. For unsupported platform fixtures, guard their registration and explain why; **do not use Jasmine `pending()`/`xit()` as skips**, because Phoenix's reporter treats pending specs as failures. Distinguish simulated platform checks from native execution coverage and verify the expected spec count on each platform.
- Validate through the connected test runner: confirm the suite is discovered with the expected spec count and category, run it using MCP, and check `get_test_results` for completion and failures. A zero-spec run is not a pass. When adding shared test bootstrap code, also run the unit category to check that other suites still work.
- **Never use `awaits(number)`** (fixed-time waits) in tests — they cause flaky failures. Always use `awaitsFor(condition)` to wait for a specific condition to become true.
- Use `editor.*` APIs (e.g. `editor.document.getText()`, `editor.getCursorPos()`, `editor.setSelection()`) instead of accessing `editor._codeMirror` directly.
- Tests should be independent — no shared mutable state between `it()` blocks. Use `FILE_CLOSE` with `{ _forceClose: true }` to clean up.
Expand Down
30 changes: 16 additions & 14 deletions gulpfile.js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const rename = require("gulp-rename");
const execSync = require('child_process').execSync;
const terser = require('terser');

const copyOptions = copyThirdPartyLibs.copyOptions;

function cleanDist() {
return del(['dist', 'dist-test']);
}
Expand Down Expand Up @@ -136,7 +138,7 @@ function _deletePhoenixProSourceFolder() {
* @returns {*}
*/
function makeDistAll() {
return src(['src/**/*', 'src/.*/*.*'])
return src(['src/**/*', 'src/.*/*.*'], copyOptions)
.pipe(dest('dist'));
}

Expand Down Expand Up @@ -170,7 +172,7 @@ function makeJSDist() {

// we had to do this as prettier is non minifiable
function makeJSPrettierDist() {
return src(["src/thirdparty/prettier/**/*"])
return src(["src/thirdparty/prettier/**/*"], copyOptions)
.pipe(dest('dist/thirdparty/prettier'));
}

Expand All @@ -182,12 +184,12 @@ function makeNonMinifyDist() {
"src/LiveDevelopment/BrowserScripts/RemoteFunctions.js",
"src/extensionsIntegrated/phoenix-pro/onboarding/**/*",
"src/extensionsIntegrated/phoenix-pro/unit-tests/**/*",
"src/mdViewer/**/*"], {base: 'src'})
"src/mdViewer/**/*"], {...copyOptions, base: 'src'})
.pipe(dest('dist'));
}

function makeDistNonJS() {
return src(['src/**/*', 'src/.*/*.*', '!src/**/*.js'])
return src(['src/**/*', 'src/.*/*.*', '!src/**/*.js'], copyOptions)
.pipe(dest('dist'));
}

Expand Down Expand Up @@ -230,40 +232,40 @@ function zipTestFiles() {
'test/**',
'test/**/.*',
'!test/thirdparty/**',
'!test/test_folders.zip'])
'!test/test_folders.zip'], copyOptions)
.pipe(zip('test_folders.zip'))
.pipe(dest('test/'));
}

function zipDefaultProjectFiles() {
return src(['src/assets/default-project/en/**'])
return src(['src/assets/default-project/en/**'], copyOptions)
.pipe(zip('en.zip'))
.pipe(dest('src/assets/default-project/'));
}

// sample projects
function zipSampleProjectBootstrapBlog() {
return src(['src/assets/sample-projects/bootstrap-blog/**'])
return src(['src/assets/sample-projects/bootstrap-blog/**'], copyOptions)
.pipe(zip('bootstrap-blog.zip'))
.pipe(dest('src/assets/sample-projects/'));
}
function zipSampleProjectExplore() {
return src(['src/assets/sample-projects/explore/**'])
return src(['src/assets/sample-projects/explore/**'], copyOptions)
.pipe(zip('explore.zip'))
.pipe(dest('src/assets/sample-projects/'));
}
function zipSampleProjectHTML5() {
return src(['src/assets/sample-projects/HTML5/**'])
return src(['src/assets/sample-projects/HTML5/**'], copyOptions)
.pipe(zip('HTML5.zip'))
.pipe(dest('src/assets/sample-projects/'));
}
function zipSampleProjectDashboard() {
return src(['src/assets/sample-projects/dashboard/**'])
return src(['src/assets/sample-projects/dashboard/**'], copyOptions)
.pipe(zip('dashboard.zip'))
.pipe(dest('src/assets/sample-projects/'));
}
function zipSampleProjectHomePages() {
return src(['src/assets/sample-projects/home-pages/**'])
return src(['src/assets/sample-projects/home-pages/**'], copyOptions)
.pipe(zip('home-pages.zip'))
.pipe(dest('src/assets/sample-projects/'));
}
Expand Down Expand Up @@ -991,12 +993,12 @@ function createDistCacheManifestDev() {
}

function copyDistToDistTestFolder() {
return src('dist/**/*')
return src('dist/**/*', copyOptions)
.pipe(dest('dist-test/src'));
}

function copyTestToDistTestFolder() {
return src('test/**/*')
return src('test/**/*', copyOptions)
.pipe(dest('dist-test/test'));
}

Expand Down Expand Up @@ -1094,7 +1096,7 @@ function _patchMinifiedCSSInDistIndex() {
return new Promise((resolve)=>{
let content = fs.readFileSync("dist/index.html", "utf8");
if(!content.includes(`<link rel="stylesheet/less" type="text/css" href="styles/brackets.less">`)){
throw new Error(`Could not locate string <link rel="stylesheet/less" type="text/css" href="styles/brackets.less"> in file dist/index.html`)
throw new Error(`Could not locate string <link rel="stylesheet/less" type="text/css" href="styles/brackets.less"> in file dist/index.html`);
}
content = content.replace(
`<link rel="stylesheet/less" type="text/css" href="styles/brackets.less">`,
Expand Down
15 changes: 13 additions & 2 deletions gulpfile.js/thirdparty-lib-copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ const path = require('path');
// removed require('merge-stream') node module. it gives wired glob behavior and some files goes missing
const rename = require("gulp-rename");

const copyOptions = {
/**
* Preserve CSS encoding markers; keep Gulp's BOM stripping for other file types.
* @param {Object} file Vinyl file being read.
* @returns {boolean} Whether to remove the file's BOM.
*/
removeBOM(file) {
return file.extname.toLowerCase() !== ".css";
}
};

// individual third party copy
function copyLicence(filePath, name) {
Expand All @@ -38,7 +48,7 @@ function copyLicence(filePath, name) {

function renameFile(filePath, newName, destPath) {
console.log(`Renaming file ${filePath} to ${newName}`);
return src(filePath)
return src(filePath, copyOptions)
.pipe(rename(newName))
.pipe(dest(destPath));
}
Expand All @@ -65,7 +75,7 @@ function downloadFile(url, outputPath) {

function copyFiles(srcPathList, dstPath) {
console.log(`Copying files ${dstPath}`);
return src(srcPathList)
return src(srcPathList, copyOptions)
.pipe(dest(dstPath));
}

Expand Down Expand Up @@ -304,5 +314,6 @@ function _patchTernLib() {
});
}

exports.copyOptions = copyOptions;
exports.copyAll = series(copyThirdPartyLibs, _patchAcornLib, _patchTernLib);
exports.copyAllDebug = series(copyThirdPartyLibs, copyThirdPartyDebugLibs, _patchAcornLib, _patchTernLib);
98 changes: 57 additions & 41 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@
"tern": "^0.24.3",
"tinycolor2": "^1.4.2",
"underscore": "^1.13.4",
"@xterm/xterm": "^6.0.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-search": "^0.16.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/addon-webgl": "^0.19.0"
"@xterm/xterm": "6.1.0-beta.304",
"@xterm/addon-fit": "0.12.0-beta.301",
"@xterm/addon-search": "0.17.0-beta.301",
"@xterm/addon-web-links": "0.13.0-beta.301",
"@xterm/addon-webgl": "0.20.0-beta.300"
}
}
2 changes: 1 addition & 1 deletion src-node/claude-code-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ exports.getCliSpawnProfile = async function (params) {
return Object.assign({}, result, { command: null, args: [] });
}
const profile = CliLocator.getSpawnProfile(result.path);
return Object.assign({}, result, { command: profile.command, args: profile.args });
return Object.assign({}, result, profile);
};

/**
Expand Down
Loading
Loading