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
8 changes: 0 additions & 8 deletions .gemini/settings.json

This file was deleted.

5 changes: 3 additions & 2 deletions .github/workflows/engine-parity.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Engine parity

# The scaffold's generated devDependencies must match the engine release its `neo.mjs` pin resolves to, read from npm.
# The scaffold's generated devDependencies and overrides must match the engine release its `neo.mjs` pin resolves to,
# read from npm.
# See scripts/check-engine-parity.mjs for the scope and why the engine is never read from a checkout.
on:
push:
Expand All @@ -23,5 +24,5 @@ jobs:
- name: Parity check contract
run: node scripts/test-check-engine-parity.mjs

- name: Generated devDependencies match the pinned engine release
- name: Generated devDependencies and overrides match the pinned engine release
run: node scripts/check-engine-parity.mjs
52 changes: 11 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@ Please take a couple of minutes to read this <a href="https://github.com/neomjs/
## Content
1. <a href="#quick-overview">Quick Overview</a>
2. <a href="#script-options">Script Options</a>
3. <a href="#ai-tooling">AI Tooling & MCP Servers</a>
4. <a href="#starting-a-local-web-server">Starting a local web-server</a>
5. <a href="#viewing-your-app-in-development-mode">Viewing your app in development mode</a>
6. <a href="#viewing-your-app-in-dist-modes">Viewing your app in dist modes</a>
7. <a href="#workspace-content">Workspace Content</a>
8. <a href="#packagejson-scripts">package.json Scripts</a>
9. <a href="#working-on-your-new-neomjs-app">Working on your new neo.mjs App</a>
10. <a href="#learning-neomjs">Learning neo.mjs</a>
11. <a href="#feedback-and-questions">Feedback and Questions</a>
12. <a href="#alternative-options-to-create-an-app">Alternative options to create an App</a>
13. <a href="#kudos">Kudos</a>
3. <a href="#starting-a-local-web-server">Starting a local web-server</a>
4. <a href="#viewing-your-app-in-development-mode">Viewing your app in development mode</a>
5. <a href="#viewing-your-app-in-dist-modes">Viewing your app in dist modes</a>
6. <a href="#workspace-content">Workspace Content</a>
7. <a href="#packagejson-scripts">package.json Scripts</a>
8. <a href="#working-on-your-new-neomjs-app">Working on your new neo.mjs App</a>
9. <a href="#learning-neomjs">Learning neo.mjs</a>
10. <a href="#feedback-and-questions">Feedback and Questions</a>
11. <a href="#alternative-options-to-create-an-app">Alternative options to create an App</a>
12. <a href="#kudos">Kudos</a>

## Quick Overview
> npx neo-app
Expand Down Expand Up @@ -62,27 +61,6 @@ Choose the themes:
Of course, you can combine the options, e.g.:
> npx neo-app -w workspace -n MyApp -t all

## AI Tooling & MCP Servers
The created workspace comes pre-configured with Model Context Protocol (MCP) servers for advanced AI integration (e.g., with Gemini or Claude).

**Important:** These servers rely on the `neo.mjs` framework being fully installed.
A `postinstall` script is included in your workspace `package.json` to automatically ensure the `neo.mjs` node module is correctly set up:

> "postinstall": "cd node_modules/neo.mjs && npm i"

If you run into issues with the AI scripts, try running `npm install` in your workspace root to trigger this hook.

### Configuration (.env)
To use the AI features, you must create a `.env` file in your workspace root. This file is git-ignored by default to keep your credentials safe.

Add the following keys:
```env
GEMINI_API_KEY=your_api_key_here
GH_TOKEN=your_github_token_here
```
* `GEMINI_API_KEY`: Required for the generative AI agents.
* `GH_TOKEN`: Required for the GitHub Workflow MCP server to interact with your repository.

## Starting a local web-server
By default, npx neo-app will start a webpack dev-server right after the build.

Expand Down Expand Up @@ -136,7 +114,6 @@ framework version update would require to re-create it)
your app as well as all neo.mjs examples
6. node_modules => all related dependencies which are required for the build scripts & the dev-server
7. package.json => defines scripts and dependencies.
8. .gemini => contains configuration for the Gemini CLI and MCP servers.

## package.json Scripts

Expand All @@ -154,14 +131,7 @@ You can run each script via
* `build-threads`: builds main, data & vdom (or any combinations) for dev and / or prod.
* `create-app`: add an additional app to your project. You can also trigger npx neo-app multiple times.
* `generate-docs-json`: When you change your app code (e.g. adding new files) and want to see those changes inside the Docs app, you need to run this script to update the content.

### AI & MCP Scripts
* `ai:mcp-client`: Starts the MCP CLI client.
* `ai:mcp-server-github-workflow`: Starts the MCP server for GitHub interactions (requires config).
* `ai:mcp-server-knowledge-base`: Starts the MCP server for Knowledge Base access (requires config).
* `ai:mcp-server-memory-core`: Starts the MCP server for persistent memory (requires config).
* `ai:server`: Starts the ChromaDB server for the knowledge base (default port).
* `ai:server-memory`: Starts the ChromaDB server for memory core (port 8001).
* `bundle-browser-deps`: builds the browser bundles the engine loads from `node_modules/neo.mjs/dist/`. The npm package ships none of them. `npm install` builds the two the engine imports at startup (`parse5`, `marked`); run this script for the ones loaded on demand (Mermaid, Monaco, highlight.js).

## Working on your new neo.mjs App
It is recommended to use the development mode (non dist version) for developing your App(s).
Expand Down
3 changes: 0 additions & 3 deletions ai/mcp/server/github-workflow/config.json

This file was deleted.

9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
{
"name" : "neo-app",
"version" : "12.1.0",
"version" : "13.2.0",
"description" : "Neo.mjs: Create app via npx",
"type" : "module",
"repository" : {
"type": "git",
"url" : "git+https://github.com/neomjs/create-app.git"
},
"scripts" : {
"ai:mcp-server-github-workflow": "node ./node_modules/neo.mjs/ai/mcp/server/github-workflow/mcp-stdio.mjs -c ./ai/mcp/server/github-workflow/config.json",
"neo-app" : "node ./tasks/neo-app.mjs",
"test" : "node scripts/test-check-engine-parity.mjs && node scripts/check-engine-parity.mjs"
"neo-app": "node ./tasks/neo-app.mjs",
"test" : "node scripts/test-check-engine-parity.mjs && node scripts/check-engine-parity.mjs"
},
"bin" : {
"neo-app": "./tasks/neo-app.mjs"
Expand Down Expand Up @@ -39,7 +38,7 @@
"envinfo" : "^7.21.0",
"fs-extra" : "^11.3.4",
"inquirer" : "^13.3.0",
"neo.mjs" : "^12.1.0"
"neo.mjs" : "^13.2.0"
},
"funding" : {
"type": "GitHub Sponsors",
Expand Down
51 changes: 46 additions & 5 deletions scripts/check-engine-parity.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node
/**
* @summary Fails when a devDependency the scaffold generates differs from the engine release its `neo.mjs` pin resolves
* to.
* @summary Fails when a devDependency or an override the scaffold generates differs from the engine release its
* `neo.mjs` pin resolves to.
*
* `tasks/createPackageJson.mjs` writes every scaffolded app's devDependencies, which advertise the engine's own ranges.
* They once fell a whole major behind with nothing noticing. Whether a scaffolded app's bundle scripts build from these
Expand All @@ -12,6 +12,10 @@
* because a scaffolded app installs the published package and a checkout can be ahead of it. The pin decides which
* release: the newest published version its range allows. A package only the scaffold declares has no engine
* counterpart to drift from, so it is listed and not compared.
*
* Overrides are compared in full, both ways. npm reads them only in the root project, so the engine's floors reach an
* app's own install only through the scaffold's copy. An override only the scaffold declares is stale, or it is what a
* registry answer without the field looks like; both need a look, so both fail.
*/

import {execFileSync} from 'node:child_process';
Expand Down Expand Up @@ -50,19 +54,46 @@ export function findDrift(scaffold, engine) {
}
}

/**
* @summary JSON with object keys sorted at every depth, so two equal override trees compare equal.
* @param {*} value
* @returns {String|undefined}
*/
const canonical = value => value !== null && typeof value === 'object'
? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
: JSON.stringify(value);

/**
* @summary Compares the scaffold's overrides with the engine's, across the names either side declares.
* @param {Object} [scaffold={}] The generated overrides
* @param {Object} [engine={}] The engine release's overrides
* @returns {Object[]} `{name, scaffold, engine}` for each override that differs; a side that lacks it reads `undefined`
*/
export function findOverrideDrift(scaffold = {}, engine = {}) {
return [...new Set([...Object.keys(engine), ...Object.keys(scaffold)])]
.filter(name => canonical(scaffold[name]) !== canonical(engine[name]))
.map(name => ({name, scaffold: scaffold[name], engine: engine[name]}))
}

/**
* @param {...String} args
* @returns {*} the parsed `npm view … --json` answer
* @returns {*} the parsed `npm view … --json` answer, `undefined` for a field the release does not declare
*/
const npmView = (...args) => JSON.parse(execFileSync('npm', ['view', ...args, '--json'], {encoding: 'utf8'}));
const npmView = (...args) => {
const text = execFileSync('npm', ['view', ...args, '--json'], {encoding: 'utf8'}).trim();

// An absent field answers with no output at all
return text ? JSON.parse(text) : undefined
};

if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
const
packageJson = generatedPackageJson(),
range = packageJson.dependencies['neo.mjs'],
// One matching version answers as a string, several as an ascending list
version = [].concat(npmView(`neo.mjs@${range}`, 'version')).at(-1),
{drift, scaffoldOnly} = findDrift(packageJson.devDependencies, npmView(`neo.mjs@${version}`, 'devDependencies'));
{drift, scaffoldOnly} = findDrift(packageJson.devDependencies, npmView(`neo.mjs@${version}`, 'devDependencies')),
overrideDrift = findOverrideDrift(packageJson.overrides, npmView(`neo.mjs@${version}`, 'overrides'));

if (scaffoldOnly.length > 0) {
console.log(`check-engine-parity: not compared, no engine counterpart: ${scaffoldOnly.join(', ')}`)
Expand All @@ -77,4 +108,14 @@ if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToP
} else {
console.log(`check-engine-parity: every devDependency the scaffold shares with neo.mjs@${version} matches.`)
}

if (overrideDrift.length > 0) {
console.error(`check-engine-parity: ${overrideDrift.length} generated overrides differ from neo.mjs@${version}:`);
overrideDrift.forEach(({name, scaffold, engine}) =>
console.error(` ${name} scaffold ${JSON.stringify(scaffold) ?? '(none)'} engine ${JSON.stringify(engine) ?? '(none)'}`));
console.error('Copy the engine\'s overrides into tasks/createPackageJson.mjs.');
process.exitCode = 1
} else {
console.log(`check-engine-parity: the scaffold's overrides equal neo.mjs@${version}'s.`)
}
}
24 changes: 22 additions & 2 deletions scripts/test-check-engine-parity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
* divergence, stays quiet on a match, and lists what it does not compare.
*/

import assert from 'node:assert/strict';
import {findDrift, generatedPackageJson} from './check-engine-parity.mjs';
import assert from 'node:assert/strict';
import {findDrift, findOverrideDrift, generatedPackageJson} from './check-engine-parity.mjs';

// ── It reads the package.json the scaffold writes ──────────────────────────────────────────────────
{
const packageJson = generatedPackageJson();

assert.match(packageJson.dependencies['neo.mjs'], /^\^\d+\.\d+\.\d+$/, 'the engine pin is read from the generator');
assert.ok(Object.keys(packageJson.devDependencies).length > 10, 'and so are the devDependencies');
assert.ok(Object.keys(packageJson.overrides).length > 0, 'and the overrides');
}

// ── A planted divergence is reported, a match is not ───────────────────────────────────────────────
Expand All @@ -36,4 +37,23 @@ import {findDrift, generatedPackageJson} from './check-engine-parity.mjs';
assert.deepEqual(scaffoldOnly, ['chromadb'])
}

// ── Overrides compare in full, both ways: npm reads them only in the root project ──────────────────
{
const engine = {'gray-matter': {'js-yaml': '^3.15.2'}, 'lodash-es': '^4.18.0'};

assert.deepEqual(findOverrideDrift({'gray-matter': {'kind-of': '^6.0.3', 'js-yaml': '^3.15.2'}},
{'gray-matter': {'js-yaml': '^3.15.2', 'kind-of': '^6.0.3'}}), [], 'equal overrides pass in any key order');

assert.deepEqual(findOverrideDrift({'lodash-es': '^4.18.0'}, engine),
[{name: 'gray-matter', scaffold: undefined, engine: {'js-yaml': '^3.15.2'}}], 'an engine floor the scaffold lacks is drift');

assert.deepEqual(findOverrideDrift({...engine, 'gray-matter': {'js-yaml': '^3.14.0'}}, engine),
[{name: 'gray-matter', scaffold: {'js-yaml': '^3.14.0'}, engine: {'js-yaml': '^3.15.2'}}], 'a nested range that differs is drift');

assert.deepEqual(findOverrideDrift(engine, undefined).map(({name}) => name), ['gray-matter', 'lodash-es'],
'overrides the release does not declare are drift, which also catches an answer without the field');

assert.deepEqual(findOverrideDrift(undefined, undefined), [], 'neither side declaring overrides is a match')
}

console.log('check-engine-parity: contract arms green.');
34 changes: 0 additions & 34 deletions tasks/createGeminiSettings.mjs

This file was deleted.

4 changes: 1 addition & 3 deletions tasks/createGitignore.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ export default {
".DS_Store",
"Thumbs.db",
"",
"# Local AI Knowledge Base",
"# Local environment",
".env",
"chroma-neo-knowledge-base/",
"chroma-neo-memory-core/",
"",
"# Playwright test results",
"test/playwright/test-results/",
Expand Down
31 changes: 0 additions & 31 deletions tasks/createMcpConfigs.mjs

This file was deleted.

Loading
Loading