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
98 changes: 21 additions & 77 deletions build/api-docs-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ const path = require('path');
const glob = require('glob');
const util = require('util');
const crypto = require('crypto');
const exec = util.promisify(require('child_process').exec);
const jsdoc2md = require('jsdoc-to-markdown');

// Promisify the glob function to enable async/await usage
const globPromise = util.promisify(glob);

// Constants
const SRC_DIR = './src';
const BUILD_DIR = './build';
const TEMP_DIR = path.join(BUILD_DIR, 'temp');
const MD_FILES_DIR = path.join('./docs', 'API-Reference');
const TEMP_CHECK_DIR = path.join(BUILD_DIR, 'check_copy');
const BATCH_SIZE = 12;


Expand All @@ -26,14 +23,6 @@ async function createDir(dirPath) {
}


/**
* Remove directory
* @param {string} dirPath - The path to the directory to remove
*/
async function removeDir(dirPath) {
await fs.rm(dirPath, { recursive: true, force: true });
}

/**
* Responsible to extract file names with their parent dir
* Ex :- docs/API-Reference/worker/WorkerComm -> worker/WorkerComm
Expand Down Expand Up @@ -201,25 +190,6 @@ function normalizeLineEndings(content) {
}


/**
* Compare two files based on their MD5 hash values
* @param {string} file1 - Path to the first file
* @param {string} file2 - Path to the second file
* @returns {Promise<boolean>} - True if files are different, false otherwise
*/
async function areFilesDifferent(file1, file2) {
const [content1, content2] = await Promise.all([
fs.readFile(file1, 'utf-8').then(normalizeLineEndings),
fs.readFile(file2, 'utf-8').then(normalizeLineEndings)
]);

const hash1 = crypto.createHash('md5').update(content1).digest('hex');
const hash2 = crypto.createHash('md5').update(content2).digest('hex');

return hash1 !== hash2;
}


/**
* Generates markdown documentation for a given JavaScript file
* @param {string} file Path to the JavaScript file
Expand All @@ -230,54 +200,40 @@ async function generateMarkdown(file, relativePath) {
const fileName = path.basename(file, '.js');

const modifiedContent = modifyJs(content, fileName);
await fs.writeFile(file, modifiedContent, 'utf-8');

// Generate markdown using jsdoc-to-markdown as a library
const markdownContent = await jsdoc2md.render({ source: modifiedContent });
const newContent = normalizeLineEndings(
modifyMarkdown(markdownContent, path.join(relativePath, fileName))
);

const outputDir = path.join(MD_FILES_DIR, relativePath);
await createDir(outputDir);

const outputFileName = path.join(outputDir, `${fileName}.md`);
const tempOutputFileName = path.join(
TEMP_CHECK_DIR, `${fileName}_temp.md`
);

await createDir(TEMP_CHECK_DIR);

// Generate markdown to a temporary file
await exec(`npx jsdoc-to-markdown ${file} > ${tempOutputFileName}`);

let markdownContent = await fs.readFile(tempOutputFileName, 'utf-8');
const updatedMarkdownContent = modifyMarkdown(
markdownContent, path.join(relativePath, fileName)
);

await fs.writeFile(tempOutputFileName, updatedMarkdownContent, 'utf-8');

const fileExists = await fs.access(outputFileName).then(() => true).catch(
() => false
);
// Compare with existing file in memory to avoid unnecessary writes
let existingContent = null;
try {
existingContent = await fs.readFile(outputFileName, 'utf-8');
} catch (e) {
// File doesn't exist yet
}

const shouldUpdate = !fileExists || await areFilesDifferent(
outputFileName, tempOutputFileName
);
const newHash = crypto.createHash('md5').update(newContent).digest('hex');
const existingHash = existingContent
? crypto.createHash('md5').update(existingContent).digest('hex')
: null;

if (shouldUpdate) {
await fs.rename(tempOutputFileName, outputFileName);
if (newHash !== existingHash) {
await fs.writeFile(outputFileName, newContent, 'utf-8');
console.log(`Updated ${outputFileName}`);
} else {
await fs.unlink(tempOutputFileName);
console.log(`No changes in ${outputFileName}`);
}
}


/**
* Cleans up temp directories
*/
async function cleanupTempDir() {
await removeDir(TEMP_CHECK_DIR);
}


/**
* Driver function
*/
Expand All @@ -288,7 +244,6 @@ async function driver() {
await getExistingMarkdownFiles(jsFiles);

console.log(`Found ${jsFiles.length} files to process`);
await createDir(TEMP_DIR);
await createDir(MD_FILES_DIR);

for (let i = 0; i < jsFiles.length; i += BATCH_SIZE) {
Expand All @@ -297,25 +252,14 @@ async function driver() {
const relativePath = path.relative(
SRC_DIR, path.dirname(file)
);
const tempDirPath = path.join(TEMP_DIR, relativePath);
await createDir(tempDirPath);

const fileName = path.basename(file);
const destPath = path.join(tempDirPath, fileName);
await fs.copyFile(file, destPath);

await generateMarkdown(destPath, relativePath);
await generateMarkdown(file, relativePath);
console.log(`Processed ${file}`);
}));
}

await removeDir(TEMP_DIR);
await cleanupTempDir();
console.log("All files processed successfully!");
} catch (error) {
console.error("An error occurred:", error);
await removeDir(TEMP_DIR).catch(() => { });
await cleanupTempDir().catch(() => { });
}
}

Expand Down
138 changes: 127 additions & 11 deletions docs/API-Reference/NodeConnector.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,126 @@ const NodeConnector = brackets.getModule("NodeConnector")
<a name="module_NodeConnector"></a>

## NodeConnector
Node Connector Communication ModuleThis module simplifies communication between Node.js and Phoenix (phcode). A `NodeConnector` acts as an intermediary,allowing you to execute functions in Node.js from Phoenix and vice versa. You can use the `execPeer` method to callfunctions on the other side and handle communication seamlessly. Use `triggerPeer` to trigger eventson the other side.## Setting Up a `NodeConnector`To establish communication between two modules, such as `x.js` in Phoenix and `y.js` in Node.js, follow these steps:### Create `NodeConnector` in Phoenix (`x.js`)
Node Connector Communication Module

This module simplifies communication between Node.js and Phoenix (phcode). A `NodeConnector` acts as an intermediary,
allowing you to execute functions in Node.js from Phoenix and vice versa. You can use the `execPeer` method to call
functions on the other side and handle communication seamlessly. Use `triggerPeer` to trigger events
on the other side.

## Setting Up a `NodeConnector`

To establish communication between two modules, such as `x.js` in Phoenix and `y.js` in Node.js, follow these steps:

### Create `NodeConnector` in Phoenix (`x.js`)

**Example**
```jsconst NodeConnector = require('NodeConnector');const XY_NODE_CONNECTOR_ID = 'ext_x_y'; // Use a unique IDlet nodeConnector = NodeConnector.createNodeConnector(XY_NODE_CONNECTOR_ID, exports);exports.modifyImage = async function(imageName, imageArrayBuffer) { // Perform image operations with the imageArrayBuffer // To return an ArrayBuffer, return an object with a `buffer` key. return { operationDone: 'colored, cropped', buffer: imageArrayBuffer, };};```### Create `NodeConnector` in Node.js (`y.js`)
```js
const NodeConnector = require('NodeConnector');
const XY_NODE_CONNECTOR_ID = 'ext_x_y'; // Use a unique ID
let nodeConnector = NodeConnector.createNodeConnector(XY_NODE_CONNECTOR_ID, exports);

exports.modifyImage = async function(imageName, imageArrayBuffer) {
// Perform image operations with the imageArrayBuffer
// To return an ArrayBuffer, return an object with a `buffer` key.
return {
operationDone: 'colored, cropped',
buffer: imageArrayBuffer,
};
};
```

### Create `NodeConnector` in Node.js (`y.js`)
**Example**
```jsconst XY_NODE_CONNECTOR_ID = 'ext_x_y'; // Use the same unique IDlet nodeConnector = global.createNodeConnector(XY_NODE_CONNECTOR_ID, exports);exports.getPWDRelative = async function(subPath) { return process.cwd + '/' + subPath;};```With these steps, a `NodeConnector` is set up, enabling two-way communication.## Executing FunctionsTo call a Node.js function from Phoenix, use the `execPeer` method.
```js
const XY_NODE_CONNECTOR_ID = 'ext_x_y'; // Use the same unique ID
let nodeConnector = global.createNodeConnector(XY_NODE_CONNECTOR_ID, exports);

exports.getPWDRelative = async function(subPath) {
return process.cwd + '/' + subPath;
};
```

With these steps, a `NodeConnector` is set up, enabling two-way communication.

## Executing Functions

To call a Node.js function from Phoenix, use the `execPeer` method.
**Example**
```js// In `x.js` (Phoenix)const fullPath = await nodeConnector.execPeer('getPWDRelative', 'sub/path.html');```To execute a Phoenix function from Node.js and transfer binary data, pass an optional ArrayBuffer.
```js
// In `x.js` (Phoenix)
const fullPath = await nodeConnector.execPeer('getPWDRelative', 'sub/path.html');
```

To execute a Phoenix function from Node.js and transfer binary data, pass an optional ArrayBuffer.
**Example**
```js// In `y.js` (Node.js)const { operationDone, buffer } = await nodeConnector.execPeer('modifyImage', {name:'theHills.png'}, imageAsArrayBuffer);```## Event HandlingThe `NodeConnector` object implements all the APIs supported by `utils/EventDispatcher`. You can trigger and listento events between Node.js and Phoenix using the `triggerPeer` and (`on`, `one` or `off`) methods.
```js
// In `y.js` (Node.js)
const { operationDone, buffer } = await nodeConnector.execPeer('modifyImage', {name:'theHills.png'}, imageAsArrayBuffer);
```

## Event Handling

The `NodeConnector` object implements all the APIs supported by `utils/EventDispatcher`. You can trigger and listen
to events between Node.js and Phoenix using the `triggerPeer` and (`on`, `one` or `off`) methods.
**Example**
```js// In `y.js` (Node.js)nodeConnector.on('phoenixProjectOpened', (_event, projectPath) => { console.log(projectPath);});nodeConnector.one('phoenixProjectOpened', (_event, projectPath) => { console.log(projectPath + "will be received only once");});```To raise an event from Phoenix to Node.js:
```js
// In `y.js` (Node.js)
nodeConnector.on('phoenixProjectOpened', (_event, projectPath) => {
console.log(projectPath);
});

nodeConnector.one('phoenixProjectOpened', (_event, projectPath) => {
console.log(projectPath + "will be received only once");
});
```

To raise an event from Phoenix to Node.js:
**Example**
```js// In `x.js` (Phoenix)nodeConnector.triggerPeer('phoenixProjectOpened', '/x/project/folder');```To Switch off events
```js
// In `x.js` (Phoenix)
nodeConnector.triggerPeer('phoenixProjectOpened', '/x/project/folder');
```

To Switch off events
**Example**
```jsnodeConnector.off('phoenixProjectOpened'); // will switch off all event handlers of that name.```By Default, all events handlers with the eventName is removed when you call `nodeConnector.off(eventName)` fn.To selectively switch off event handlers, please see reference for `utils/EventDispatcher` module.### Handling ArrayBuffer Data in Function ExecutionWhen executing functions that send or receive binary data, ensure that the functions are asynchronous and accept anoptional ArrayBuffer as a parameter. To return binary data, use an object with a `buffer` key.Example of calling a function in Node.js with binary data transfer:
```js
nodeConnector.off('phoenixProjectOpened'); // will switch off all event handlers of that name.
```

By Default, all events handlers with the eventName is removed when you call `nodeConnector.off(eventName)` fn.
To selectively switch off event handlers, please see reference for `utils/EventDispatcher` module.

### Handling ArrayBuffer Data in Function Execution

When executing functions that send or receive binary data, ensure that the functions are asynchronous and accept an
optional ArrayBuffer as a parameter. To return binary data, use an object with a `buffer` key.

Example of calling a function in Node.js with binary data transfer:
**Example**
```js// In `y.js` (Node.js)const { operationDone, buffer } = await nodeConnector.execPeer('modifyImage', {name:'name.png'}, imageArrayBuffer);```### Handling ArrayBuffer Data in Event HandlingUse the `triggerPeer` method to send binary data in events. Include the ArrayBuffer as an optional parameter.Example of sending binary data in an event from Phoenix to Node.js:
```js
// In `y.js` (Node.js)
const { operationDone, buffer } = await nodeConnector.execPeer('modifyImage', {name:'name.png'}, imageArrayBuffer);
```

### Handling ArrayBuffer Data in Event Handling

Use the `triggerPeer` method to send binary data in events. Include the ArrayBuffer as an optional parameter.

Example of sending binary data in an event from Phoenix to Node.js:
**Example**
```js// In `x.js` (Phoenix)const imageArrayBuffer = getSomeImageArrayBuffer(); // Get the ArrayBuffernodeConnector.triggerPeer('imageEdited', 'name.png', imageArrayBuffer);```## Caveats- Be cautious when sending large binary data, as it may affect performance and memory usage. Transferring large data is fully supported, but be mindful of performance.- Functions called with `execPeer` and `triggerPeer` must be asynchronous and accept a single argument. An optional second argument can be used to transfer large binary data as an ArrayBuffer.For more event handling operations and details, refer to the documentation for the `utils/EventDispatcher` module.
```js
// In `x.js` (Phoenix)
const imageArrayBuffer = getSomeImageArrayBuffer(); // Get the ArrayBuffer
nodeConnector.triggerPeer('imageEdited', 'name.png', imageArrayBuffer);
```
## Caveats
- Be cautious when sending large binary data, as it may affect performance and memory usage. Transferring large
data is fully supported, but be mindful of performance.
- Functions called with `execPeer` and `triggerPeer` must be asynchronous and accept a single argument. An optional
second argument can be used to transfer large binary data as an ArrayBuffer.

For more event handling operations and details, refer to the documentation for the `utils/EventDispatcher` module.

* [NodeConnector](#module_NodeConnector)
* [.createNodeConnector(nodeConnectorID, moduleExports)](#module_NodeConnector..createNodeConnector) ⇒ <code>Object</code>
Expand All @@ -39,7 +139,23 @@ Node Connector Communication Module This module simplifies communication betwee
<a name="module_NodeConnector..createNodeConnector"></a>

### NodeConnector.createNodeConnector(nodeConnectorID, moduleExports) ⇒ <code>Object</code>
Creates a new node connector with the specified ID and module exports.Returns a NodeConnector Object (which is an EventDispatcher withadditional `execPeer` and `triggerPeer` methods. `peer` here means, if you are executing `execPeer`in Phoenix, it will execute the named function in node side, and vice versa. You can right away startusing `execPeer`, `triggerPeer`(to send/receive events) APIs without waiting to check if theother side nodeConnector is created.Note: If the NodeConnector has not been created on the other end, requests made with `execPeer` or`triggerPeer` will be temporarily queued for up to 10 seconds to allow time for the connector to be created.If the connector is not created within this timeout period, all queued `execPeer` requests will be rejected,and all queued events will be dropped. It is recommended to call the `createNodeConnector` API on both endswithin a timeframe of less than 10 seconds(ideally same time) for seamless communication.- execPeer: A function that executes a peer function with specified parameters.- triggerPeer: A function that triggers an event to be sent to a peer.- Also contains all the APIs supported by `utils/EventDispatcher` module.
Creates a new node connector with the specified ID and module exports.

Returns a NodeConnector Object (which is an EventDispatcher with
additional `execPeer` and `triggerPeer` methods. `peer` here means, if you are executing `execPeer`
in Phoenix, it will execute the named function in node side, and vice versa. You can right away start
using `execPeer`, `triggerPeer`(to send/receive events) APIs without waiting to check if the
other side nodeConnector is created.

Note: If the NodeConnector has not been created on the other end, requests made with `execPeer` or
`triggerPeer` will be temporarily queued for up to 10 seconds to allow time for the connector to be created.
If the connector is not created within this timeout period, all queued `execPeer` requests will be rejected,
and all queued events will be dropped. It is recommended to call the `createNodeConnector` API on both ends
within a timeframe of less than 10 seconds(ideally same time) for seamless communication.

- execPeer: A function that executes a peer function with specified parameters.
- triggerPeer: A function that triggers an event to be sent to a peer.
- Also contains all the APIs supported by `utils/EventDispatcher` module.

**Kind**: inner method of [<code>NodeConnector</code>](#module_NodeConnector)
**Returns**: <code>Object</code> - - A NodeConnector Object. Also contains all the APIs supported by `utils/EventDispatcher` module.
Expand Down
Loading
Loading