mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
Compare commits
5 Commits
cline-dev
...
cline-dev-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1cc4b9b98 | ||
|
|
6aa45320e9 | ||
|
|
a104a3a8d0 | ||
|
|
e3b682d037 | ||
|
|
199b4f9cb2 |
34
.clinerules
34
.clinerules
@@ -1520,3 +1520,37 @@ Starting with Subtask 1 now..."
|
||||
6. **Learn from errors** - If you hit limits, that task was too large
|
||||
|
||||
**Remember**: It's better to complete 3 small subtasks successfully than fail on 1 large task repeatedly.
|
||||
|
||||
---
|
||||
|
||||
## 16. Code Comments Language
|
||||
|
||||
**All code comments must be in English**, regardless of the user's language. This ensures:
|
||||
|
||||
- Consistent codebase for international collaboration
|
||||
- Better compatibility with AI tools
|
||||
- Easier code review and maintenance
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: English comments
|
||||
function calculateTotal(items: Item[]): number {
|
||||
// Sum up all item prices
|
||||
return items.reduce((sum, item) => sum + item.price, 0);
|
||||
}
|
||||
|
||||
// ❌ BAD: Non-English comments
|
||||
function calculateTotal(items: Item[]): number {
|
||||
// Additionner tous les prix des articles
|
||||
return items.reduce((sum, item) => sum + item.price, 0);
|
||||
}
|
||||
```
|
||||
|
||||
This rule applies to:
|
||||
|
||||
- Inline comments
|
||||
- Function/class documentation (JSDoc)
|
||||
- Block comments explaining logic
|
||||
- TODO/FIXME notes
|
||||
- Commit messages (covered in Git Workflow section)
|
||||
|
||||
**Exception**: User-facing strings in UI components may be in any language (they will be localized later).
|
||||
|
||||
@@ -4,6 +4,196 @@ This document captures important discoveries and gotchas encountered during Open
|
||||
|
||||
---
|
||||
|
||||
## 🐛 CRITICAL: Project.json Structure - Missing `graph` Object (Jan 9, 2026)
|
||||
|
||||
### The Silent Crash: Cannot Read Properties of Undefined (reading 'comments')
|
||||
|
||||
**Context**: Phase 0 TASK-010 - New project creation failed with `TypeError: Cannot read properties of undefined (reading 'comments')`. After three previous failed attempts, the root cause was finally identified: incorrect JSON structure in programmatic project creation.
|
||||
|
||||
**The Problem**: The programmatically generated project.json had `nodes` array directly in the component object, but the schema requires a `graph` object containing `roots`, `connections`, and `comments`.
|
||||
|
||||
**Root Cause**: Misunderstanding of the project.json schema hierarchy:
|
||||
|
||||
```
|
||||
Component
|
||||
├─ name
|
||||
├─ id
|
||||
├─ metadata
|
||||
└─ graph ← REQUIRED
|
||||
├─ roots ← Was "nodes" (WRONG)
|
||||
├─ connections
|
||||
└─ comments ← Error occurred here
|
||||
```
|
||||
|
||||
**The Broken Pattern**:
|
||||
|
||||
```typescript
|
||||
// ❌ WRONG - Missing graph wrapper, comments field
|
||||
const minimalProject = {
|
||||
name: name,
|
||||
components: [
|
||||
{
|
||||
name: 'App',
|
||||
ports: [],
|
||||
visual: true,
|
||||
visualStateTransitions: [],
|
||||
nodes: [
|
||||
// ☠️ Should be graph.roots, not nodes
|
||||
{
|
||||
id: guid(),
|
||||
type: 'Group'
|
||||
// ...
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// ComponentModel.fromJSON calls NodeGraphModel.fromJSON(json.graph)
|
||||
// But json.graph is undefined!
|
||||
// NodeGraphModel.fromJSON tries to access json.comments
|
||||
// BOOM: Cannot read properties of undefined (reading 'comments')
|
||||
```
|
||||
|
||||
**The Correct Pattern**:
|
||||
|
||||
```typescript
|
||||
// ✅ RIGHT - Complete structure with graph object
|
||||
const minimalProject = {
|
||||
name: name,
|
||||
components: [
|
||||
{
|
||||
name: 'App',
|
||||
id: guid(), // Component needs id
|
||||
graph: {
|
||||
// Graph wrapper required
|
||||
roots: [
|
||||
// Not "nodes"
|
||||
{
|
||||
id: guid(),
|
||||
type: 'Group',
|
||||
x: 0,
|
||||
y: 0,
|
||||
parameters: {},
|
||||
ports: [],
|
||||
children: [
|
||||
{
|
||||
id: guid(),
|
||||
type: 'Text',
|
||||
x: 50,
|
||||
y: 50,
|
||||
parameters: { text: 'Hello World!' },
|
||||
ports: [],
|
||||
children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
connections: [], // Required array
|
||||
comments: [] // Required array (caused the error!)
|
||||
},
|
||||
metadata: {} // Component metadata
|
||||
}
|
||||
],
|
||||
settings: {},
|
||||
metadata: {
|
||||
// Project metadata
|
||||
title: name,
|
||||
description: 'A new Noodl project'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Why This Was Hard to Debug**:
|
||||
|
||||
1. **Error message was misleading**: "reading 'comments'" suggested a problem with comments, not missing `graph` object
|
||||
2. **Deep call stack**: Error originated 3 levels deep (ProjectModel → ComponentModel → NodeGraphModel)
|
||||
3. **No schema documentation**: project.json structure wasn't formally documented
|
||||
4. **Template file was truncated**: The actual template (`project-truncated.json`) had incomplete structure
|
||||
5. **Multiple fix attempts**: Previous fixes addressed symptoms (path resolution) not root cause (structure)
|
||||
|
||||
**The Fix Journey**:
|
||||
|
||||
- **Attempt 1**: Path resolution with `__dirname` - FAILED (webpack bundling issue)
|
||||
- **Attempt 2**: Path resolution with `process.cwd()` - FAILED (wrong directory)
|
||||
- **Attempt 3**: Programmatic creation - FAILED (incomplete structure)
|
||||
- **Attempt 4**: Complete structure with `graph` object - SUCCESS ✅
|
||||
|
||||
**Required Fields Hierarchy**:
|
||||
|
||||
```typescript
|
||||
// Complete minimal project structure
|
||||
{
|
||||
name: string,
|
||||
components: [{
|
||||
name: string,
|
||||
id: string, // ← REQUIRED
|
||||
graph: { // ← REQUIRED wrapper
|
||||
roots: [...], // ← Was incorrectly "nodes"
|
||||
connections: [], // ← REQUIRED array
|
||||
comments: [] // ← REQUIRED array (error occurred here)
|
||||
},
|
||||
metadata: {} // ← REQUIRED object
|
||||
}],
|
||||
settings: {}, // ← REQUIRED object
|
||||
metadata: { // ← Project-level metadata
|
||||
title: string,
|
||||
description: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**How to Identify This Issue**:
|
||||
|
||||
1. **Error**: `Cannot read properties of undefined (reading 'comments')`
|
||||
2. **Stack trace**: Shows `NodeGraphModel.fromJSON` at line accessing `json.comments`
|
||||
3. **Symptom**: Project creation appears to work but crashes when loading
|
||||
4. **Root cause**: `ComponentModel.fromJSON` passes `json.graph` to `NodeGraphModel.fromJSON`, but `json.graph` is `undefined`
|
||||
|
||||
**Critical Rules**:
|
||||
|
||||
1. **Components have `graph` objects, not `nodes` arrays directly** - The nodes live in `graph.roots`
|
||||
2. **Always include `comments` and `connections` arrays** - Even if empty, they must exist
|
||||
3. **Component needs `id` field** - Can't rely on auto-generation
|
||||
4. **Use actual template structure as reference** - Don't invent your own schema
|
||||
5. **Test project creation end-to-end** - Not just file writing, but also loading
|
||||
|
||||
**Related Code Paths**:
|
||||
|
||||
```typescript
|
||||
// The error chain:
|
||||
ProjectModel.fromJSON(json)
|
||||
→ calls ComponentModel.fromJSON(json.components[i])
|
||||
→ calls NodeGraphModel.fromJSON(json.graph) // ← json.graph is undefined!
|
||||
→ accesses json.comments // ← BOOM!
|
||||
```
|
||||
|
||||
**Prevention**: When creating projects programmatically, always use this checklist:
|
||||
|
||||
- [ ] Component has `id` field
|
||||
- [ ] Component has `graph` object (not `nodes` array)
|
||||
- [ ] `graph.roots` array exists (not `nodes`)
|
||||
- [ ] `graph.connections` array exists (can be empty)
|
||||
- [ ] `graph.comments` array exists (can be empty)
|
||||
- [ ] Component has `metadata` object (can be empty)
|
||||
- [ ] Project has `settings` object (can be empty)
|
||||
- [ ] Project has `metadata` object with `title` and `description`
|
||||
|
||||
**Time Lost**: ~6 hours across three failed attempts before finding root cause
|
||||
|
||||
**Location**:
|
||||
|
||||
- Fixed in: `packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts` (lines 288-321)
|
||||
- Error source: `packages/noodl-editor/src/editor/src/models/nodegraphmodel/NodeGraphModel.ts` (line 57)
|
||||
- Task: Phase 0 TASK-010 Project Creation Bug Fix
|
||||
- CHANGELOG: `dev-docs/tasks/phase-0-foundation-stabilisation/TASK-010-project-creation-bug-fix/CHANGELOG.md`
|
||||
|
||||
**Impact**: This was a P0 blocker preventing all new users from creating projects. The fix allows project creation to work correctly without requiring external templates.
|
||||
|
||||
**Keywords**: project.json, schema, graph object, NodeGraphModel, ComponentModel, fromJSON, comments, roots, Cannot read properties of undefined, project creation, minimal project, structure
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Canvas Overlay Pattern: React Over HTML5 Canvas (Jan 3, 2026)
|
||||
|
||||
### The Transform Trick: CSS scale() + translate() for Automatic Coordinate Transformation
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# TASK-010: Project Creation Bug Fix - CHANGELOG
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Date**: January 9, 2026
|
||||
**Priority**: P0 - Critical Blocker
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Fixed critical bug preventing new project creation. The issue was an incorrect project.json structure in programmatic project generation - missing the required `graph` object wrapper and the `comments` array, causing `TypeError: Cannot read properties of undefined (reading 'comments')`.
|
||||
|
||||
---
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Fixed Project Structure in LocalProjectsModel.ts
|
||||
|
||||
**File**: `packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts`
|
||||
|
||||
**Problem**: The programmatically generated project.json had an incorrect structure:
|
||||
|
||||
- Used `nodes` array directly in component (should be `graph.roots`)
|
||||
- Missing `graph` object wrapper
|
||||
- Missing `comments` array (causing the error)
|
||||
- Missing `connections` array
|
||||
- Missing component `id` field
|
||||
|
||||
**Solution**: Corrected the structure to match the schema:
|
||||
|
||||
```typescript
|
||||
// BEFORE (INCORRECT)
|
||||
{
|
||||
name: 'App',
|
||||
ports: [],
|
||||
visual: true,
|
||||
visualStateTransitions: [],
|
||||
nodes: [...] // ❌ Wrong location
|
||||
}
|
||||
|
||||
// AFTER (CORRECT)
|
||||
{
|
||||
name: 'App',
|
||||
id: guid(), // ✅ Added
|
||||
graph: { // ✅ Added wrapper
|
||||
roots: [...], // ✅ Renamed from 'nodes'
|
||||
connections: [], // ✅ Added
|
||||
comments: [] // ✅ Added (was causing error)
|
||||
},
|
||||
metadata: {} // ✅ Added
|
||||
}
|
||||
```
|
||||
|
||||
**Lines Modified**: 288-321
|
||||
|
||||
### 2. Added Debug Logging
|
||||
|
||||
Added console logging for better debugging:
|
||||
|
||||
- Success message: "Project created successfully: {name}"
|
||||
- Error messages for failure cases
|
||||
|
||||
---
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### The Error Chain
|
||||
|
||||
```
|
||||
ProjectModel.fromJSON(json)
|
||||
→ ComponentModel.fromJSON(json.components[i])
|
||||
→ NodeGraphModel.fromJSON(json.graph) // ← json.graph was undefined!
|
||||
→ accesses json.comments // ← BOOM: Cannot read properties of undefined
|
||||
```
|
||||
|
||||
### Why Previous Attempts Failed
|
||||
|
||||
1. **Attempt 1** (Path resolution with `__dirname`): Webpack bundling issue
|
||||
2. **Attempt 2** (Path resolution with `process.cwd()`): Wrong directory
|
||||
3. **Attempt 3** (Programmatic creation): Incomplete structure (this attempt)
|
||||
|
||||
### The Final Solution
|
||||
|
||||
Understanding that the schema requires:
|
||||
|
||||
- Component needs `id` field
|
||||
- Component needs `graph` object (not `nodes` array)
|
||||
- `graph` must contain `roots`, `connections`, and `comments` arrays
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Testing Performed
|
||||
|
||||
1. ✅ Created new project from dashboard
|
||||
2. ✅ Project opened without errors
|
||||
3. ✅ Console showed: "Project created successfully: alloha"
|
||||
4. ✅ Component "App" visible in editor
|
||||
5. ✅ Text node with "Hello World!" present
|
||||
6. ✅ Project can be saved and reopened
|
||||
|
||||
### Success Criteria Met
|
||||
|
||||
- [x] New users can create projects successfully
|
||||
- [x] No console errors during project creation
|
||||
- [x] Projects load correctly after creation
|
||||
- [x] All components are visible in the editor
|
||||
- [x] Error message resolved: "Cannot read properties of undefined (reading 'comments')"
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts**
|
||||
- Lines 288-321: Fixed project.json structure
|
||||
- Lines 324-345: Added better error logging
|
||||
|
||||
---
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
1. **dev-docs/reference/LEARNINGS.md**
|
||||
- Added comprehensive entry documenting the project.json structure
|
||||
- Included prevention checklist for future programmatic project creation
|
||||
- Documented the error chain and debugging journey
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Before**: P0 blocker - New users could not create projects at all
|
||||
**After**: ✅ Project creation works correctly
|
||||
|
||||
**User Experience**:
|
||||
|
||||
- No more cryptic error messages
|
||||
- Smooth onboarding for new users
|
||||
- Reliable project creation
|
||||
|
||||
---
|
||||
|
||||
## Related Issues
|
||||
|
||||
- Unblocks user onboarding
|
||||
- Prerequisite for TASK-009 (template system refactoring)
|
||||
- Fixes recurring issue that had three previous failed attempts
|
||||
|
||||
---
|
||||
|
||||
## Notes for Future Developers
|
||||
|
||||
### Project.json Schema Requirements
|
||||
|
||||
When creating projects programmatically, always include:
|
||||
|
||||
```typescript
|
||||
{
|
||||
name: string,
|
||||
components: [{
|
||||
name: string,
|
||||
id: string, // Required
|
||||
graph: { // Required wrapper
|
||||
roots: [...], // Not "nodes"
|
||||
connections: [], // Required (can be empty)
|
||||
comments: [] // Required (can be empty)
|
||||
},
|
||||
metadata: {} // Required (can be empty)
|
||||
}],
|
||||
settings: {}, // Required
|
||||
metadata: { // Project metadata
|
||||
title: string,
|
||||
description: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prevention Checklist
|
||||
|
||||
Before creating a project programmatically:
|
||||
|
||||
- [ ] Component has `id` field
|
||||
- [ ] Component has `graph` object (not `nodes`)
|
||||
- [ ] `graph.roots` array exists
|
||||
- [ ] `graph.connections` array exists
|
||||
- [ ] `graph.comments` array exists
|
||||
- [ ] Component has `metadata` object
|
||||
- [ ] Project has `settings` object
|
||||
- [ ] Project has `metadata` with title/description
|
||||
|
||||
---
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. **Schema documentation is critical**: The lack of formal project.json schema documentation made this harder to debug
|
||||
2. **Error messages can be misleading**: "reading 'comments'" suggested comments were the problem, not the missing `graph` object
|
||||
3. **Test end-to-end**: Don't just test file writing - test loading the created project
|
||||
4. **Use real templates as reference**: The truncated template file wasn't helpful; needed to examine actual working projects
|
||||
|
||||
---
|
||||
|
||||
**Completed by**: Cline (AI Assistant)
|
||||
**Reviewed by**: Richard (User)
|
||||
**Date Completed**: January 9, 2026
|
||||
@@ -0,0 +1,320 @@
|
||||
# TASK-010: Critical Bug - Project Creation Fails Due to Incomplete JSON Structure
|
||||
|
||||
**Status**: ✅ COMPLETED
|
||||
**Priority**: URGENT (P0 - Blocker)
|
||||
**Complexity**: Medium
|
||||
**Estimated Effort**: 1 day
|
||||
**Actual Effort**: ~1 hour
|
||||
**Completed**: January 9, 2026
|
||||
|
||||
## Problem Statement
|
||||
|
||||
**Users cannot create new projects** - a critical blocker that has occurred repeatedly despite multiple fix attempts. The issue manifests with the error:
|
||||
|
||||
```
|
||||
TypeError: Cannot read properties of undefined (reading 'comments')
|
||||
at NodeGraphModel.fromJSON (NodeGraphModel.ts:57:1)
|
||||
at ComponentModel.fromJSON (componentmodel.ts:44:1)
|
||||
at ProjectModel.fromJSON (projectmodel.ts:165:1)
|
||||
```
|
||||
|
||||
## Impact
|
||||
|
||||
- **Severity**: P0 - Blocks all new users
|
||||
- **Affected Users**: Anyone trying to create a new project
|
||||
- **Workaround**: None available
|
||||
- **User Frustration**: HIGH ("ça commence à être vraiment agaçant!")
|
||||
|
||||
## History of Failed Attempts
|
||||
|
||||
### Attempt 1: LocalTemplateProvider with relative paths (January 8, 2026)
|
||||
|
||||
**Issue**: Path resolution failed with `__dirname` in webpack bundles
|
||||
|
||||
```
|
||||
Error: Hello World template not found at: ./project-examples/version 1.1.0/template-project
|
||||
```
|
||||
|
||||
### Attempt 2: LocalTemplateProvider with process.cwd() (January 8, 2026)
|
||||
|
||||
**Issue**: `process.cwd()` pointed to wrong directory
|
||||
|
||||
```
|
||||
Error: Hello World template not found at: /Users/tw/dev/OpenNoodl/OpenNoodl/packages/noodl-editor/project-examples/...
|
||||
```
|
||||
|
||||
### Attempt 3: Programmatic project creation (January 8, 2026)
|
||||
|
||||
**Issue**: Incomplete JSON structure missing required fields
|
||||
|
||||
```typescript
|
||||
const minimalProject = {
|
||||
name: name,
|
||||
components: [
|
||||
{
|
||||
name: 'App',
|
||||
ports: [],
|
||||
visual: true,
|
||||
visualStateTransitions: [],
|
||||
nodes: [
|
||||
/* ... */
|
||||
]
|
||||
}
|
||||
],
|
||||
settings: {},
|
||||
metadata: {
|
||||
/* ... */
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Error**: `Cannot read properties of undefined (reading 'comments')`
|
||||
|
||||
This indicates the structure is missing critical fields expected by `NodeGraphModel.fromJSON()`.
|
||||
|
||||
## Root Causes
|
||||
|
||||
1. **Incomplete understanding of project.json schema**
|
||||
|
||||
- No formal schema documentation
|
||||
- Required fields not documented
|
||||
- Nested structure requirements unclear
|
||||
|
||||
2. **Missing graph/node metadata**
|
||||
|
||||
- `comments` field expected but not provided
|
||||
- Possibly other required fields: `connections`, `roots`, `graph`, etc.
|
||||
|
||||
3. **No validation before project creation**
|
||||
- Projects created without structure validation
|
||||
- Errors only caught during loading
|
||||
- No helpful error messages about missing fields
|
||||
|
||||
## Required Investigation
|
||||
|
||||
### 1. Analyze Complete Project Structure
|
||||
|
||||
- [ ] Find and analyze a working project.json
|
||||
- [ ] Document ALL required fields at each level
|
||||
- [ ] Identify which fields are truly required vs optional
|
||||
- [ ] Document field types and default values
|
||||
|
||||
### 2. Analyze NodeGraphModel.fromJSON
|
||||
|
||||
- [ ] Find the actual fromJSON implementation
|
||||
- [ ] Document what fields it expects
|
||||
- [ ] Understand the `comments` field requirement
|
||||
- [ ] Check for other hidden dependencies
|
||||
|
||||
### 3. Analyze ComponentModel.fromJSON
|
||||
|
||||
- [ ] Document the component structure requirements
|
||||
- [ ] Understand visual vs non-visual components
|
||||
- [ ] Document the graph/nodes relationship
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Option A: Use Existing Template (RECOMMENDED)
|
||||
|
||||
Instead of creating from scratch, use the actual template project:
|
||||
|
||||
```typescript
|
||||
// 1. Bundle template-project as a static asset
|
||||
// 2. Copy it properly during build
|
||||
// 3. Reference it correctly at runtime
|
||||
|
||||
const templateAsset = require('../../../assets/templates/hello-world/project.json');
|
||||
const project = JSON.parse(JSON.stringify(templateAsset)); // Deep clone
|
||||
project.name = projectName;
|
||||
// Write to disk
|
||||
```
|
||||
|
||||
**Pros**:
|
||||
|
||||
- Uses validated structure
|
||||
- Guaranteed to work
|
||||
- Easy to maintain
|
||||
- Can add more templates later
|
||||
|
||||
**Cons**:
|
||||
|
||||
- Requires webpack configuration
|
||||
- Larger bundle size
|
||||
|
||||
### Option B: Complete Programmatic Structure
|
||||
|
||||
Document and implement the full structure:
|
||||
|
||||
```typescript
|
||||
const completeProject = {
|
||||
name: name,
|
||||
components: [
|
||||
{
|
||||
name: 'App',
|
||||
ports: [],
|
||||
visual: true,
|
||||
visualStateTransitions: [],
|
||||
graph: {
|
||||
roots: [
|
||||
/* root node ID */
|
||||
],
|
||||
comments: [], // REQUIRED!
|
||||
connections: []
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: guid(),
|
||||
type: 'Group',
|
||||
x: 0,
|
||||
y: 0,
|
||||
parameters: {},
|
||||
ports: [],
|
||||
children: [
|
||||
/* ... */
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
settings: {},
|
||||
metadata: {
|
||||
title: name,
|
||||
description: 'A new Noodl project'
|
||||
},
|
||||
// Other potentially required fields
|
||||
version: '1.1.0',
|
||||
variants: []
|
||||
// ... etc
|
||||
};
|
||||
```
|
||||
|
||||
**Pros**:
|
||||
|
||||
- No external dependencies
|
||||
- Smaller bundle
|
||||
- Full control
|
||||
|
||||
**Cons**:
|
||||
|
||||
- Complex to maintain
|
||||
- Easy to miss required fields
|
||||
- Will break with schema changes
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Investigation (2-3 hours)
|
||||
|
||||
- [ ] Find a working project.json file
|
||||
- [ ] Document its complete structure
|
||||
- [ ] Find NodeGraphModel/ComponentModel fromJSON implementations
|
||||
- [ ] Document all required fields
|
||||
- [ ] Create schema documentation
|
||||
|
||||
### Phase 2: Quick Fix (1 hour)
|
||||
|
||||
- [ ] Implement Option A (use template as asset)
|
||||
- [ ] Configure webpack to bundle template
|
||||
- [ ] Update LocalProjectsModel to use bundled template
|
||||
- [ ] Test project creation
|
||||
- [ ] Verify project opens correctly
|
||||
|
||||
### Phase 3: Validation (1 hour)
|
||||
|
||||
- [ ] Add project JSON schema validation
|
||||
- [ ] Validate before writing to disk
|
||||
- [ ] Provide helpful error messages
|
||||
- [ ] Add unit tests for project creation
|
||||
|
||||
### Phase 4: Documentation (1 hour)
|
||||
|
||||
- [ ] Document project.json schema
|
||||
- [ ] Add examples of minimal valid projects
|
||||
- [ ] Document how to create custom templates
|
||||
- [ ] Update LEARNINGS.md with findings
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### Investigation
|
||||
|
||||
- Find: `NodeGraphModel` (likely in `packages/noodl-editor/src/editor/src/models/`)
|
||||
- Find: `ComponentModel` (same location)
|
||||
- Find: Valid project.json (check existing projects or tests)
|
||||
|
||||
### Implementation
|
||||
|
||||
- `packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts`
|
||||
- Fix project creation logic
|
||||
- `packages/noodl-editor/webpackconfigs/webpack.renderer.dev.js`
|
||||
- Add template asset bundling if needed
|
||||
- `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
|
||||
- Add validation logic
|
||||
|
||||
### Documentation
|
||||
|
||||
- `dev-docs/reference/PROJECT-JSON-SCHEMA.md` (NEW)
|
||||
- `dev-docs/reference/LEARNINGS.md`
|
||||
- `dev-docs/reference/COMMON-ISSUES.md`
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Manual Tests
|
||||
|
||||
- [ ] Create new project from dashboard
|
||||
- [ ] Verify project opens without errors
|
||||
- [ ] Verify "App" component is visible
|
||||
- [ ] Verify nodes are editable
|
||||
- [ ] Verify project saves correctly
|
||||
- [ ] Close and reopen project
|
||||
|
||||
### Regression Tests
|
||||
|
||||
- [ ] Test with existing projects
|
||||
- [ ] Test with template-based projects
|
||||
- [ ] Test empty project creation
|
||||
- [ ] Test project import
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- [ ] Test project JSON generation
|
||||
- [ ] Test JSON validation
|
||||
- [ ] Test error handling
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] New users can create projects successfully
|
||||
- [ ] No console errors during project creation
|
||||
- [ ] Projects load correctly after creation
|
||||
- [ ] All components are visible in the editor
|
||||
- [ ] Projects can be saved and reopened
|
||||
- [ ] Solution works in both dev and production
|
||||
- [ ] Comprehensive documentation exists
|
||||
- [ ] Tests prevent regression
|
||||
|
||||
## Related Issues
|
||||
|
||||
- Original bug report: Console error "Cannot read properties of undefined (reading 'comments')"
|
||||
- Related to TASK-009-template-system-refactoring (future enhancement)
|
||||
- Impacts user onboarding and first-time experience
|
||||
|
||||
## Post-Fix Actions
|
||||
|
||||
1. **Update TASK-009**: Reference this fix as prerequisite
|
||||
2. **Add to LEARNINGS.md**: Document the project.json schema learnings
|
||||
3. **Add to COMMON-ISSUES.md**: Document this problem and solution
|
||||
4. **Create schema documentation**: Formal PROJECT-JSON-SCHEMA.md
|
||||
5. **Add validation**: Prevent future similar issues
|
||||
|
||||
## Notes
|
||||
|
||||
- This is the THIRD attempt to fix this issue
|
||||
- Problem is recurring due to lack of understanding of required schema
|
||||
- Proper investigation and documentation needed this time
|
||||
- Must validate before considering complete
|
||||
|
||||
---
|
||||
|
||||
**Created**: January 9, 2026
|
||||
**Last Updated**: January 9, 2026
|
||||
**Assignee**: TBD
|
||||
**Blocked By**: None
|
||||
**Blocks**: User onboarding, TASK-009
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 3: Editor UX Overhaul - Progress Tracker
|
||||
|
||||
**Last Updated:** 2026-01-07
|
||||
**Last Updated:** 2026-01-09
|
||||
**Overall Status:** 🟡 In Progress
|
||||
|
||||
---
|
||||
@@ -9,18 +9,18 @@
|
||||
|
||||
| Metric | Value |
|
||||
| ------------ | ------- |
|
||||
| Total Tasks | 9 |
|
||||
| Completed | 3 |
|
||||
| In Progress | 0 |
|
||||
| Not Started | 6 |
|
||||
| **Progress** | **33%** |
|
||||
| Total Tasks | 10 |
|
||||
| Completed | 4 |
|
||||
| In Progress | 1 |
|
||||
| Not Started | 5 |
|
||||
| **Progress** | **40%** |
|
||||
|
||||
---
|
||||
|
||||
## Task Status
|
||||
|
||||
| Task | Name | Status | Notes |
|
||||
| --------- | ----------------------- | -------------- | --------------------------------------------- |
|
||||
| --------- | ------------------------ | -------------- | --------------------------------------------- |
|
||||
| TASK-001 | Dashboard UX Foundation | 🟢 Complete | Tabbed navigation done |
|
||||
| TASK-001B | Launcher Fixes | 🟢 Complete | All 4 subtasks implemented |
|
||||
| TASK-002 | GitHub Integration | 🟢 Complete | OAuth + basic features done |
|
||||
@@ -30,6 +30,7 @@
|
||||
| TASK-005 | Deployment Automation | 🔴 Not Started | Planning docs only, no implementation |
|
||||
| TASK-006 | Expressions Overhaul | 🔴 Not Started | Enhanced expression nodes |
|
||||
| TASK-007 | App Config | 🟡 In Progress | Runtime ✅, UI mostly done (Monaco debugging) |
|
||||
| TASK-009 | Template System Refactor | 🟢 Complete | Embedded templates with type safety |
|
||||
|
||||
---
|
||||
|
||||
@@ -44,7 +45,8 @@
|
||||
## Recent Updates
|
||||
|
||||
| Date | Update |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 2026-01-09 | TASK-009 complete: Embedded template system implemented |
|
||||
| 2026-01-07 | Audit completed: corrected TASK-001B, TASK-005 status |
|
||||
| 2026-01-07 | Added TASK-006 and TASK-007 to tracking |
|
||||
| 2026-01-07 | TASK-008 moved to Phase 6 (UBA) |
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
# TASK-009: Template System Refactoring
|
||||
|
||||
**Status**: 🟢 Complete (Backend)
|
||||
**Priority**: Medium
|
||||
**Complexity**: Medium
|
||||
**Actual Effort**: 1 day (Backend implementation)
|
||||
|
||||
## Context
|
||||
|
||||
The current project template system has several issues:
|
||||
|
||||
- Path resolution fails in webpack bundles (`__dirname` doesn't work correctly)
|
||||
- No proper template provider for local/bundled templates
|
||||
- Template loading depends on external URLs or fragile file paths
|
||||
- New projects currently use a programmatic workaround (minimal project.json generation)
|
||||
|
||||
## Current Temporary Solution
|
||||
|
||||
As of January 2026, new projects are created programmatically in `LocalProjectsModel.ts`:
|
||||
|
||||
```typescript
|
||||
// Create a minimal Hello World project programmatically
|
||||
const minimalProject = {
|
||||
name: name,
|
||||
components: [
|
||||
/* basic App component with Text node */
|
||||
],
|
||||
settings: {},
|
||||
metadata: {
|
||||
/* ... */
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
This works but is not ideal for:
|
||||
|
||||
- Creating rich starter templates
|
||||
- Allowing custom/community templates
|
||||
- Supporting multiple bundled templates (e.g., "Hello World", "Dashboard", "E-commerce")
|
||||
|
||||
## Goals
|
||||
|
||||
### Primary Goals
|
||||
|
||||
1. **Robust Template Loading**: Support templates in both development and production
|
||||
2. **Local Templates**: Bundle templates with the editor that work reliably
|
||||
3. **Template Gallery**: Support multiple built-in templates
|
||||
4. **Custom Templates**: Allow users to create and share templates
|
||||
|
||||
### Secondary Goals
|
||||
|
||||
1. Template versioning and migration
|
||||
2. Template metadata (screenshots, descriptions, categories)
|
||||
3. Template validation before project creation
|
||||
4. Template marketplace integration (future)
|
||||
|
||||
## Proposed Architecture
|
||||
|
||||
### 1. Template Storage Options
|
||||
|
||||
**Option A: Embedded Templates (Recommended)**
|
||||
|
||||
- Store templates as JSON structures in TypeScript files
|
||||
- Import and use directly (no file I/O)
|
||||
- Bundle with webpack automatically
|
||||
- Example:
|
||||
|
||||
```typescript
|
||||
export const helloWorldTemplate: ProjectTemplate = {
|
||||
name: 'Hello World',
|
||||
components: [
|
||||
/* ... */
|
||||
],
|
||||
settings: {
|
||||
/* ... */
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Option B: Asset-Based Templates**
|
||||
|
||||
- Store templates in `packages/noodl-editor/assets/templates/`
|
||||
- Copy to build output during webpack build
|
||||
- Use proper asset loading (webpack copy plugin)
|
||||
- Access via runtime asset path resolution
|
||||
|
||||
**Option C: Hybrid Approach**
|
||||
|
||||
- Small templates: embedded in code
|
||||
- Large templates: assets with proper bundling
|
||||
- Choose based on template size/complexity
|
||||
|
||||
### 2. Template Provider Architecture
|
||||
|
||||
```typescript
|
||||
interface ProjectTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
version: string;
|
||||
thumbnail?: string;
|
||||
|
||||
// Template content
|
||||
components: ComponentDefinition[];
|
||||
settings: ProjectSettings;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface TemplateProvider {
|
||||
name: string;
|
||||
list(): Promise<ProjectTemplate[]>;
|
||||
get(id: string): Promise<ProjectTemplate>;
|
||||
canHandle(id: string): boolean;
|
||||
}
|
||||
|
||||
class EmbeddedTemplateProvider implements TemplateProvider {
|
||||
// Returns templates bundled with the editor
|
||||
}
|
||||
|
||||
class RemoteTemplateProvider implements TemplateProvider {
|
||||
// Fetches templates from Noodl docs/CDN
|
||||
}
|
||||
|
||||
class LocalFileTemplateProvider implements TemplateProvider {
|
||||
// Loads templates from user's filesystem (for custom templates)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Template Manager
|
||||
|
||||
```typescript
|
||||
class TemplateManager {
|
||||
private providers: TemplateProvider[];
|
||||
|
||||
async listTemplates(filter?: TemplateFilter): Promise<ProjectTemplate[]> {
|
||||
// Aggregates from all providers
|
||||
}
|
||||
|
||||
async getTemplate(id: string): Promise<ProjectTemplate> {
|
||||
// Finds the right provider and fetches template
|
||||
}
|
||||
|
||||
async createProjectFromTemplate(template: ProjectTemplate, projectPath: string, projectName: string): Promise<void> {
|
||||
// Creates project structure from template
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Foundation (1 day)
|
||||
|
||||
- [ ] Define `ProjectTemplate` interface
|
||||
- [ ] Create `TemplateProvider` interface
|
||||
- [ ] Implement `EmbeddedTemplateProvider`
|
||||
- [ ] Create `TemplateManager` class
|
||||
|
||||
### Phase 2: Built-in Templates (1 day)
|
||||
|
||||
- [ ] Convert current Hello World to embedded template
|
||||
- [ ] Add "Blank" template (truly empty)
|
||||
- [ ] Add "Dashboard" template (with nav + pages)
|
||||
- [ ] Add template metadata and thumbnails
|
||||
|
||||
### Phase 3: Integration (0.5 days)
|
||||
|
||||
- [ ] Update `LocalProjectsModel` to use `TemplateManager`
|
||||
- [ ] Remove programmatic project creation workaround
|
||||
- [ ] Update project creation UI to show template gallery
|
||||
- [ ] Add template preview/selection dialog
|
||||
|
||||
### Phase 4: Advanced Features (0.5 days)
|
||||
|
||||
- [ ] Implement template validation
|
||||
- [ ] Add template export functionality (for users to create templates)
|
||||
- [ ] Support template variables/parameters
|
||||
- [ ] Add template upgrade/migration system
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### New Files
|
||||
|
||||
- `packages/noodl-editor/src/editor/src/models/template/ProjectTemplate.ts`
|
||||
- `packages/noodl-editor/src/editor/src/models/template/TemplateProvider.ts`
|
||||
- `packages/noodl-editor/src/editor/src/models/template/TemplateManager.ts`
|
||||
- `packages/noodl-editor/src/editor/src/models/template/providers/EmbeddedTemplateProvider.ts`
|
||||
- `packages/noodl-editor/src/editor/src/models/template/templates/` (folder for template definitions)
|
||||
- `hello-world.ts`
|
||||
- `blank.ts`
|
||||
- `dashboard.ts`
|
||||
|
||||
### Existing Files to Update
|
||||
|
||||
- `packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts`
|
||||
- Replace programmatic project creation with template system
|
||||
- `packages/noodl-editor/src/editor/src/pages/ProjectsPage/ProjectsPage.tsx`
|
||||
- Add template selection UI
|
||||
- `packages/noodl-editor/src/editor/src/utils/forge/` (might be refactored or replaced)
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Template provider loading
|
||||
- Template validation
|
||||
- Project creation from template
|
||||
- Template merging/variables
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Create project from each bundled template
|
||||
- Verify all templates load correctly
|
||||
- Test template provider fallback
|
||||
|
||||
### Manual Tests
|
||||
|
||||
- Create projects from templates in dev mode
|
||||
- Create projects from templates in production build
|
||||
- Verify all components and nodes are created correctly
|
||||
- Test custom template import/export
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] New projects can be created from bundled templates reliably
|
||||
- [ ] Templates work identically in dev and production
|
||||
- [ ] At least 3 high-quality bundled templates available
|
||||
- [ ] Template system is extensible for future templates
|
||||
- [ ] No file path resolution issues
|
||||
- [ ] User can export their project as a template
|
||||
- [ ] Documentation for creating custom templates
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- **Template Marketplace**: Browse and download community templates
|
||||
- **Template Packages**: Include external dependencies/modules
|
||||
- **Template Generator**: AI-powered template creation
|
||||
- **Template Forking**: Modify and save as new template
|
||||
- **Template Versioning**: Update templates without breaking existing projects
|
||||
|
||||
## References
|
||||
|
||||
- Current implementation: `packages/noodl-editor/src/editor/src/utils/LocalProjectsModel.ts` (lines 295-360)
|
||||
- Failed attempt: `packages/noodl-editor/src/editor/src/utils/forge/template/providers/local-template-provider.ts`
|
||||
- Template registry: `packages/noodl-editor/src/editor/src/utils/forge/index.ts`
|
||||
|
||||
## Implementation Summary (January 9, 2026)
|
||||
|
||||
### ✅ What Was Completed
|
||||
|
||||
**Phase 1-3: Backend Implementation (Complete)**
|
||||
|
||||
1. **Type System Created**
|
||||
|
||||
- `ProjectTemplate.ts` - Complete TypeScript interfaces for templates
|
||||
- Comprehensive type definitions for components, nodes, connections, and settings
|
||||
|
||||
2. **EmbeddedTemplateProvider Implemented**
|
||||
|
||||
- Provider that handles `embedded://` protocol
|
||||
- Templates stored as TypeScript objects, bundled by webpack
|
||||
- No file I/O dependencies, works identically in dev and production
|
||||
|
||||
3. **Hello World Template Created**
|
||||
|
||||
- Structure: App → PageRouter → Page "/Home" → Text "Hello World!"
|
||||
- Clean and minimal, demonstrates Page Router usage
|
||||
- Located in `models/template/templates/hello-world.template.ts`
|
||||
|
||||
4. **Template Registry Integration**
|
||||
|
||||
- `EmbeddedTemplateProvider` registered with highest priority
|
||||
- Backward compatible with existing HTTP/Noodl Docs providers
|
||||
|
||||
5. **LocalProjectsModel Updated**
|
||||
|
||||
- Removed programmatic project creation workaround
|
||||
- Default template now uses `embedded://hello-world`
|
||||
- Maintains backward compatibility with external templates
|
||||
|
||||
6. **Documentation**
|
||||
- Complete developer guide in `models/template/README.md`
|
||||
- Instructions for creating custom templates
|
||||
- Architecture overview and best practices
|
||||
|
||||
### 📁 Files Created
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/models/template/
|
||||
├── ProjectTemplate.ts # Type definitions
|
||||
├── EmbeddedTemplateProvider.ts # Provider implementation
|
||||
├── README.md # Developer documentation
|
||||
└── templates/
|
||||
└── hello-world.template.ts # Default template
|
||||
```
|
||||
|
||||
### 📝 Files Modified
|
||||
|
||||
- `utils/forge/index.ts` - Registered EmbeddedTemplateProvider
|
||||
- `utils/LocalProjectsModel.ts` - Updated newProject() to use embedded templates
|
||||
|
||||
### 🎯 Benefits Achieved
|
||||
|
||||
✅ No more `__dirname` or `process.cwd()` path resolution issues
|
||||
✅ Templates work identically in development and production builds
|
||||
✅ Type-safe template definitions with full IDE support
|
||||
✅ Easy to add new templates - just create a TypeScript file
|
||||
✅ Maintains backward compatibility with external template URLs
|
||||
|
||||
### ⏳ Remaining Work (Future Tasks)
|
||||
|
||||
- **UI for Template Selection**: Gallery/dialog to choose templates when creating projects
|
||||
- **Additional Templates**: Blank, Dashboard, E-commerce templates
|
||||
- **Template Export**: Allow users to save their projects as templates
|
||||
- **Unit Tests**: Test suite for EmbeddedTemplateProvider
|
||||
- **Template Validation**: Verify template structure before project creation
|
||||
|
||||
### 🚀 Usage
|
||||
|
||||
```typescript
|
||||
// Create project with embedded template (automatic default)
|
||||
LocalProjectsModel.instance.newProject(callback, {
|
||||
name: 'My Project'
|
||||
// Uses 'embedded://hello-world' by default
|
||||
});
|
||||
|
||||
// Create project with specific template
|
||||
LocalProjectsModel.instance.newProject(callback, {
|
||||
name: 'My Project',
|
||||
projectTemplate: 'embedded://hello-world'
|
||||
});
|
||||
```
|
||||
|
||||
## Related Tasks
|
||||
|
||||
- **TASK-009-UI**: Template selection gallery (future)
|
||||
- **TASK-009-EXPORT**: Template export functionality (future)
|
||||
|
||||
---
|
||||
|
||||
**Created**: January 8, 2026
|
||||
**Last Updated**: January 9, 2026
|
||||
**Implementation**: January 9, 2026 (Backend complete)
|
||||
@@ -86,6 +86,9 @@ export function NodeGraphContextProvider({ children }: NodeGraphContextProviderP
|
||||
if (!nodeGraph) return;
|
||||
|
||||
function _update(model: ComponentModel) {
|
||||
// Guard against undefined model (happens on empty projects)
|
||||
if (!model) return;
|
||||
|
||||
if (isComponentModel_CloudRuntime(model)) {
|
||||
setActive('backend');
|
||||
if (SidebarModel.instance.ActiveId === 'components') {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { filesystem } from '@noodl/platform';
|
||||
|
||||
import { bugtracker } from '@noodl-utils/bugtracker';
|
||||
|
||||
// TODO: Can we merge this with ProjectModules ?
|
||||
@@ -27,6 +28,8 @@ export async function listProjectModules(project: TSFixme /* ProjectModel */): P
|
||||
}[] = [];
|
||||
|
||||
const modulesPath = project._retainedProjectDirectory + '/noodl_modules';
|
||||
|
||||
try {
|
||||
const files = await filesystem.listDirectory(modulesPath);
|
||||
|
||||
await Promise.all(
|
||||
@@ -42,6 +45,15 @@ export async function listProjectModules(project: TSFixme /* ProjectModel */): P
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
// noodl_modules folder doesn't exist (fresh/empty project)
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log('noodl_modules folder not found (fresh project), skipping module loading');
|
||||
return [];
|
||||
}
|
||||
// Re-throw other errors
|
||||
throw error;
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
@@ -50,12 +62,14 @@ export async function readProjectModules(project: TSFixme /* ProjectModel */): P
|
||||
bugtracker.debug('ProjectModel.readModules');
|
||||
|
||||
const modulesPath = project._retainedProjectDirectory + '/noodl_modules';
|
||||
const files = await filesystem.listDirectory(modulesPath);
|
||||
|
||||
project.modules = [];
|
||||
project.previews = [];
|
||||
project.componentAnnotations = {};
|
||||
|
||||
try {
|
||||
const files = await filesystem.listDirectory(modulesPath);
|
||||
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
if (file.isDirectory) {
|
||||
@@ -84,6 +98,15 @@ export async function readProjectModules(project: TSFixme /* ProjectModel */): P
|
||||
);
|
||||
|
||||
console.log(`Loaded ${project.modules.length} modules`);
|
||||
} catch (error) {
|
||||
// noodl_modules folder doesn't exist (fresh/empty project)
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log('noodl_modules folder not found (fresh project), skipping module loading');
|
||||
return [];
|
||||
}
|
||||
// Re-throw other errors
|
||||
throw error;
|
||||
}
|
||||
|
||||
return project.modules;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* EmbeddedTemplateProvider
|
||||
*
|
||||
* Provides access to templates that are embedded directly in the application code.
|
||||
* These templates are bundled with the editor and work reliably in both
|
||||
* development and production (no file I/O or path resolution issues).
|
||||
*
|
||||
* @module noodl-editor/models/template
|
||||
*/
|
||||
|
||||
import { ITemplateProvider, TemplateItem } from '../../utils/forge/template/template';
|
||||
import { ProjectTemplate } from './ProjectTemplate';
|
||||
import { helloWorldTemplate } from './templates/hello-world.template';
|
||||
|
||||
/**
|
||||
* Provider for templates that are embedded in the application code
|
||||
*/
|
||||
export class EmbeddedTemplateProvider implements ITemplateProvider {
|
||||
/**
|
||||
* Registry of all embedded templates
|
||||
* New templates should be added here
|
||||
*/
|
||||
private templates: Map<string, ProjectTemplate> = new Map([
|
||||
['hello-world', helloWorldTemplate]
|
||||
// Add more templates here as they are created
|
||||
]);
|
||||
|
||||
get name(): string {
|
||||
return 'embedded-templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available embedded templates
|
||||
* @returns Array of template items
|
||||
*/
|
||||
async list(): Promise<ReadonlyArray<TemplateItem>> {
|
||||
const items: TemplateItem[] = [];
|
||||
|
||||
for (const [id, template] of this.templates) {
|
||||
items.push({
|
||||
title: template.name,
|
||||
desc: template.description,
|
||||
category: template.category,
|
||||
iconURL: template.thumbnail || '',
|
||||
projectURL: `embedded://${id}`,
|
||||
useCloudServices: false,
|
||||
cloudServicesTemplateURL: undefined
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this provider can handle the given URL
|
||||
* @param url - The template URL to check
|
||||
* @returns True if URL starts with "embedded://"
|
||||
*/
|
||||
async canDownload(url: string): Promise<boolean> {
|
||||
return url.startsWith('embedded://');
|
||||
}
|
||||
|
||||
/**
|
||||
* "Download" (copy) the template to the destination directory
|
||||
*
|
||||
* Note: For embedded templates, we write the project.json directly
|
||||
* rather than copying files from disk.
|
||||
*
|
||||
* @param url - Template URL (e.g., "embedded://hello-world")
|
||||
* @param destination - Destination directory path
|
||||
* @returns Promise that resolves when template is written
|
||||
*/
|
||||
async download(url: string, destination: string): Promise<void> {
|
||||
// Extract template ID from URL
|
||||
const templateId = url.replace('embedded://', '');
|
||||
|
||||
const template = this.templates.get(templateId);
|
||||
if (!template) {
|
||||
throw new Error(`Unknown embedded template: ${templateId}`);
|
||||
}
|
||||
|
||||
// Get the template content (which will have its name overridden by the caller)
|
||||
const projectContent = template.content;
|
||||
|
||||
// Ensure destination directory exists
|
||||
const { filesystem } = await import('@noodl/platform');
|
||||
|
||||
// Create destination directory if it doesn't exist
|
||||
if (!filesystem.exists(destination)) {
|
||||
await filesystem.makeDirectory(destination);
|
||||
}
|
||||
|
||||
// Write project.json to destination
|
||||
const projectJsonPath = filesystem.join(destination, 'project.json');
|
||||
await filesystem.writeFile(projectJsonPath, JSON.stringify(projectContent, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific template by ID (utility method)
|
||||
* @param id - Template ID
|
||||
* @returns The template, or undefined if not found
|
||||
*/
|
||||
getTemplate(id: string): ProjectTemplate | undefined {
|
||||
return this.templates.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all template IDs (utility method)
|
||||
* @returns Array of template IDs
|
||||
*/
|
||||
getTemplateIds(): string[] {
|
||||
return Array.from(this.templates.keys());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* ProjectTemplate
|
||||
*
|
||||
* Defines the structure for project templates that can be used
|
||||
* to create new projects with pre-configured components and settings.
|
||||
*
|
||||
* @module noodl-editor/models/template
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a complete project template structure
|
||||
*/
|
||||
export interface ProjectTemplate {
|
||||
/** Unique identifier for the template */
|
||||
id: string;
|
||||
|
||||
/** Display name of the template */
|
||||
name: string;
|
||||
|
||||
/** Description of what the template provides */
|
||||
description: string;
|
||||
|
||||
/** Category for grouping templates (e.g., "Getting Started", "Dashboard") */
|
||||
category: string;
|
||||
|
||||
/** Template version (semver) */
|
||||
version: string;
|
||||
|
||||
/** Optional thumbnail/icon URL for UI display */
|
||||
thumbnail?: string;
|
||||
|
||||
/** The actual project content */
|
||||
content: ProjectContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The core content structure of a Noodl project
|
||||
*/
|
||||
export interface ProjectContent {
|
||||
/** Project name (will be overridden by user input) */
|
||||
name: string;
|
||||
|
||||
/** Array of component definitions */
|
||||
components: ComponentDefinition[];
|
||||
|
||||
/** Project-level settings */
|
||||
settings?: ProjectSettings;
|
||||
|
||||
/** Project metadata */
|
||||
metadata?: ProjectMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of a single component in the project
|
||||
*/
|
||||
export interface ComponentDefinition {
|
||||
/** Component name (e.g., "App", "/#__page__/Home") */
|
||||
name: string;
|
||||
|
||||
/** Component graph structure */
|
||||
graph?: ComponentGraph;
|
||||
|
||||
/** Whether this is a visual component */
|
||||
visual?: boolean;
|
||||
|
||||
/** Component ID (optional, will be generated if not provided) */
|
||||
id?: string;
|
||||
|
||||
/** Port definitions for the component */
|
||||
ports?: PortDefinition[];
|
||||
|
||||
/** Visual state transitions (for visual components) */
|
||||
visualStateTransitions?: unknown[];
|
||||
|
||||
/** Component metadata */
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component graph containing nodes and connections
|
||||
*/
|
||||
export interface ComponentGraph {
|
||||
/** Root nodes in the component */
|
||||
roots: NodeDefinition[];
|
||||
|
||||
/** Connections between nodes */
|
||||
connections: ConnectionDefinition[];
|
||||
|
||||
/** Comments in the graph (required by NodeGraphModel) */
|
||||
comments?: unknown[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of a single node in the component graph
|
||||
*/
|
||||
export interface NodeDefinition {
|
||||
/** Unique node ID */
|
||||
id: string;
|
||||
|
||||
/** Node type (e.g., "Group", "Text", "PageRouter") */
|
||||
type: string;
|
||||
|
||||
/** X position on canvas */
|
||||
x: number;
|
||||
|
||||
/** Y position on canvas */
|
||||
y: number;
|
||||
|
||||
/** Node parameters/properties */
|
||||
parameters: Record<string, unknown>;
|
||||
|
||||
/** Port definitions */
|
||||
ports?: PortDefinition[];
|
||||
|
||||
/** Child nodes (for visual hierarchy) */
|
||||
children?: NodeDefinition[];
|
||||
|
||||
/** Variant (for some node types) */
|
||||
variant?: string;
|
||||
|
||||
/** State parameters (for state nodes) */
|
||||
stateParameters?: Record<string, unknown>;
|
||||
|
||||
/** State transitions (for state nodes) */
|
||||
stateTransitions?: unknown[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection between two nodes
|
||||
*/
|
||||
export interface ConnectionDefinition {
|
||||
/** Source node ID */
|
||||
fromId: string;
|
||||
|
||||
/** Source port/property name */
|
||||
fromProperty: string;
|
||||
|
||||
/** Target node ID */
|
||||
toId: string;
|
||||
|
||||
/** Target port/property name */
|
||||
toProperty: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Port definition for components/nodes
|
||||
*/
|
||||
export interface PortDefinition {
|
||||
/** Port name */
|
||||
name: string;
|
||||
|
||||
/** Port type (e.g., "string", "number", "signal") */
|
||||
type: string;
|
||||
|
||||
/** Port direction ("input" or "output") */
|
||||
plug: 'input' | 'output';
|
||||
|
||||
/** Port index (for ordering) */
|
||||
index?: number;
|
||||
|
||||
/** Default value */
|
||||
default?: unknown;
|
||||
|
||||
/** Display name */
|
||||
displayName?: string;
|
||||
|
||||
/** Port group */
|
||||
group?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project-level settings
|
||||
*/
|
||||
export interface ProjectSettings {
|
||||
/** Project settings go here */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project metadata
|
||||
*/
|
||||
export interface ProjectMetadata {
|
||||
/** Project title */
|
||||
title?: string;
|
||||
|
||||
/** Project description */
|
||||
description?: string;
|
||||
|
||||
/** Other metadata fields */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
173
packages/noodl-editor/src/editor/src/models/template/README.md
Normal file
173
packages/noodl-editor/src/editor/src/models/template/README.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Template System Documentation
|
||||
|
||||
This directory contains the embedded project template system implemented in TASK-009.
|
||||
|
||||
## Overview
|
||||
|
||||
The template system allows creating new Noodl projects from pre-defined templates that are embedded directly in the application code. This ensures templates work reliably in both development and production without file path resolution issues.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
models/template/
|
||||
├── ProjectTemplate.ts # TypeScript interfaces for templates
|
||||
├── EmbeddedTemplateProvider.ts # Provider for embedded templates
|
||||
├── templates/ # Template definitions
|
||||
│ └── hello-world.template.ts # Default Hello World template
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Template Definition**: Templates are defined as TypeScript objects using the `ProjectTemplate` interface
|
||||
2. **Provider Registration**: The `EmbeddedTemplateProvider` is registered in `utils/forge/index.ts` with the highest priority
|
||||
3. **Template Usage**: When creating a new project, templates are referenced via `embedded://template-id` URLs
|
||||
4. **Project Creation**: The provider writes the template's `project.json` directly to the destination directory
|
||||
|
||||
## Creating a New Template
|
||||
|
||||
### Step 1: Define Your Template
|
||||
|
||||
Create a new file in `templates/` (e.g., `dashboard.template.ts`):
|
||||
|
||||
```typescript
|
||||
import { ProjectTemplate } from '../ProjectTemplate';
|
||||
|
||||
export const dashboardTemplate: ProjectTemplate = {
|
||||
id: 'dashboard',
|
||||
name: 'Dashboard Template',
|
||||
description: 'A dashboard with navigation and multiple pages',
|
||||
category: 'Business Apps',
|
||||
version: '1.0.0',
|
||||
thumbnail: undefined,
|
||||
|
||||
content: {
|
||||
name: 'Dashboard Project',
|
||||
components: [
|
||||
// Define your components here
|
||||
{
|
||||
name: 'App',
|
||||
visual: true,
|
||||
ports: [],
|
||||
visualStateTransitions: [],
|
||||
graph: {
|
||||
roots: [
|
||||
// Add your nodes here
|
||||
],
|
||||
connections: []
|
||||
}
|
||||
}
|
||||
],
|
||||
settings: {},
|
||||
metadata: {
|
||||
title: 'Dashboard Project',
|
||||
description: 'A complete dashboard template'
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Step 2: Register the Template
|
||||
|
||||
Add your template to `EmbeddedTemplateProvider.ts`:
|
||||
|
||||
```typescript
|
||||
import { dashboardTemplate } from './templates/dashboard.template';
|
||||
|
||||
export class EmbeddedTemplateProvider implements ITemplateProvider {
|
||||
private templates: Map<string, ProjectTemplate> = new Map([
|
||||
['hello-world', helloWorldTemplate],
|
||||
['dashboard', dashboardTemplate] // Add your template here
|
||||
]);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Use Your Template
|
||||
|
||||
Create a project with your template:
|
||||
|
||||
```typescript
|
||||
LocalProjectsModel.instance.newProject(
|
||||
(project) => {
|
||||
console.log('Project created:', project);
|
||||
},
|
||||
{
|
||||
name: 'My Dashboard',
|
||||
projectTemplate: 'embedded://dashboard'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Template Structure Reference
|
||||
|
||||
### Component Definition
|
||||
|
||||
```typescript
|
||||
{
|
||||
name: 'ComponentName', // Component name (use '/#__page__/Name' for pages)
|
||||
visual: true, // Whether this is a visual component
|
||||
ports: [], // Component ports
|
||||
visualStateTransitions: [], // State transitions
|
||||
graph: {
|
||||
roots: [/* nodes */], // Root-level nodes
|
||||
connections: [] // Connections between nodes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Node Definition
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: generateId(), // Unique node ID
|
||||
type: 'NodeType', // Node type (e.g., 'Text', 'Group', 'PageRouter')
|
||||
x: 100, // X position on canvas
|
||||
y: 100, // Y position on canvas
|
||||
parameters: { // Node parameters
|
||||
text: 'Hello',
|
||||
fontSize: { value: 16, unit: 'px' }
|
||||
},
|
||||
ports: [], // Node-specific ports
|
||||
children: [] // Child nodes (for visual hierarchy)
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use the Helper Function**: Use the `generateId()` function for generating unique IDs
|
||||
2. **Structure Over Data**: Define component structure, not specific user data
|
||||
3. **Minimal & Clear**: Keep templates simple and focused on structure
|
||||
4. **Test Both Modes**: Test templates in both development and production builds
|
||||
5. **Document Purpose**: Add JSDoc comments explaining what the template provides
|
||||
|
||||
## Default Template
|
||||
|
||||
When no template is specified in `newProject()`, the system automatically uses `embedded://hello-world` as the default template.
|
||||
|
||||
## Advantages Over Previous System
|
||||
|
||||
✅ **No Path Resolution Issues**: Templates are embedded in code, bundled by webpack
|
||||
✅ **Dev/Prod Parity**: Works identically in development and production
|
||||
✅ **Type Safety**: Full TypeScript support with interfaces
|
||||
✅ **Easy to Extend**: Add new templates by creating a file and registering it
|
||||
✅ **No External Dependencies**: No need for external template files or URLs
|
||||
|
||||
## Migration from Old System
|
||||
|
||||
The old system used:
|
||||
|
||||
- Programmatic project creation (JSON literal in code)
|
||||
- File-based templates (with path resolution issues)
|
||||
- External template URLs
|
||||
|
||||
The new system:
|
||||
|
||||
- Uses embedded template objects
|
||||
- Provides a consistent API via `templateRegistry`
|
||||
- Maintains backward compatibility with external template URLs
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 9, 2026
|
||||
**Related**: TASK-009-template-system-refactoring
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Hello World Template
|
||||
*
|
||||
* A simple starter project with:
|
||||
* - App component (root)
|
||||
* - Page Router configured
|
||||
* - Home page with "Hello World" text
|
||||
*
|
||||
* @module noodl-editor/models/template/templates
|
||||
*/
|
||||
|
||||
import { ProjectTemplate } from '../ProjectTemplate';
|
||||
|
||||
/**
|
||||
* Generate a unique ID for nodes
|
||||
*/
|
||||
function generateId(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hello World template
|
||||
* Creates a basic project with Page Router and a home page
|
||||
*/
|
||||
export const helloWorldTemplate: ProjectTemplate = {
|
||||
id: 'hello-world',
|
||||
name: 'Hello World',
|
||||
description: 'A simple starter project with a home page displaying "Hello World"',
|
||||
category: 'Getting Started',
|
||||
version: '1.0.0',
|
||||
thumbnail: undefined,
|
||||
|
||||
content: {
|
||||
name: 'Hello World Project',
|
||||
components: [
|
||||
// App component (root)
|
||||
{
|
||||
name: 'App',
|
||||
id: generateId(),
|
||||
visual: true,
|
||||
ports: [],
|
||||
visualStateTransitions: [],
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: generateId(),
|
||||
type: 'Router',
|
||||
x: 100,
|
||||
y: 100,
|
||||
parameters: {
|
||||
startPage: '/#__page__/Home'
|
||||
},
|
||||
ports: [],
|
||||
children: []
|
||||
}
|
||||
],
|
||||
connections: [],
|
||||
comments: []
|
||||
},
|
||||
metadata: {}
|
||||
},
|
||||
// Home Page component
|
||||
{
|
||||
name: '/#__page__/Home',
|
||||
id: generateId(),
|
||||
visual: true,
|
||||
ports: [],
|
||||
visualStateTransitions: [],
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: generateId(),
|
||||
type: 'Text',
|
||||
x: 100,
|
||||
y: 100,
|
||||
parameters: {
|
||||
text: 'Hello World!',
|
||||
fontSize: { value: 32, unit: 'px' },
|
||||
textAlign: 'center'
|
||||
},
|
||||
ports: [],
|
||||
children: []
|
||||
}
|
||||
],
|
||||
connections: [],
|
||||
comments: []
|
||||
},
|
||||
metadata: {}
|
||||
}
|
||||
],
|
||||
settings: {},
|
||||
metadata: {
|
||||
title: 'Hello World Project',
|
||||
description: 'A simple starter project'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -260,36 +260,37 @@ export class LocalProjectsModel extends Model {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Default template path
|
||||
const defaultTemplatePath = './external/projecttemplates/helloworld.zip';
|
||||
// No template specified - use default embedded Hello World template
|
||||
// This uses the template system implemented in TASK-009
|
||||
const defaultTemplate = 'embedded://hello-world';
|
||||
|
||||
// Check if template exists, otherwise create an empty project
|
||||
if (filesystem.exists(defaultTemplatePath)) {
|
||||
this._unzipAndLaunchProject(defaultTemplatePath, dirEntry, fn, options);
|
||||
} else {
|
||||
console.warn('Default project template not found, creating empty project');
|
||||
// For embedded templates, write directly to the project directory
|
||||
// (no need for temporary folder + copy)
|
||||
const { EmbeddedTemplateProvider } = await import('../models/template/EmbeddedTemplateProvider');
|
||||
const embeddedProvider = new EmbeddedTemplateProvider();
|
||||
|
||||
// Create minimal project.json for empty project
|
||||
const minimalProject = {
|
||||
name: name,
|
||||
components: [],
|
||||
settings: {}
|
||||
};
|
||||
await embeddedProvider.download(defaultTemplate, dirEntry);
|
||||
|
||||
await filesystem.writeFile(filesystem.join(dirEntry, 'project.json'), JSON.stringify(minimalProject, null, 2));
|
||||
|
||||
// Load the newly created empty project
|
||||
// Load the newly created project
|
||||
projectFromDirectory(dirEntry, (project) => {
|
||||
if (!project) {
|
||||
console.error('Failed to create project from template');
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
|
||||
project.name = name;
|
||||
this._addProject(project);
|
||||
project.toDirectory(project._retainedProjectDirectory, (res) => {
|
||||
if (res.result === 'success') {
|
||||
console.log('Project created successfully:', name);
|
||||
fn(project);
|
||||
});
|
||||
} else {
|
||||
console.error('Failed to save project to directory');
|
||||
fn();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
import { RuntimeType } from '@noodl-models/nodelibrary/NodeLibraryData';
|
||||
|
||||
export function getComponentModelRuntimeType(node: ComponentModel) {
|
||||
// Guard against undefined node (happens on empty projects)
|
||||
if (!node) return RuntimeType.Browser;
|
||||
|
||||
const name = node.name;
|
||||
|
||||
if (name.startsWith('/#__cloud__/')) {
|
||||
|
||||
@@ -154,6 +154,11 @@ export async function getPageRoutes(project: ProjectModel, options: IndexedPages
|
||||
}
|
||||
});
|
||||
|
||||
// Check if traverser has valid root (empty project case)
|
||||
if (!traverser.root) {
|
||||
return { routes: [], pages: [], dynamicHash: {} };
|
||||
}
|
||||
|
||||
// Fetch all the Page nodes.
|
||||
const pages: TraverseNode[] = traverser.filter((node) => node.node.typename === 'Page');
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import getDocsEndpoint from '@noodl-utils/getDocsEndpoint';
|
||||
|
||||
import { EmbeddedTemplateProvider } from '../../models/template/EmbeddedTemplateProvider';
|
||||
import { HttpTemplateProvider } from './template/providers/http-template-provider';
|
||||
import { NoodlDocsTemplateProvider } from './template/providers/noodl-docs-template-provider';
|
||||
import { TemplateRegistry } from './template/template-registry';
|
||||
|
||||
// The order of the providers matters,
|
||||
// when looking for a template it will take the first one that allows it.
|
||||
// EmbeddedTemplateProvider is first as it provides built-in templates that work reliably.
|
||||
const templateRegistry = new TemplateRegistry([
|
||||
new EmbeddedTemplateProvider(),
|
||||
new NoodlDocsTemplateProvider(getDocsEndpoint),
|
||||
new HttpTemplateProvider()
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import path from 'node:path';
|
||||
import { filesystem } from '@noodl/platform';
|
||||
|
||||
import FileSystem from '../../../filesystem';
|
||||
import { ITemplateProvider, TemplateItem, TemplateListFilter } from '../template';
|
||||
|
||||
/**
|
||||
* Provides access to locally bundled project templates.
|
||||
* This provider is used for templates that ship with the editor.
|
||||
*/
|
||||
export class LocalTemplateProvider implements ITemplateProvider {
|
||||
get name(): string {
|
||||
return 'local-templates';
|
||||
}
|
||||
|
||||
async list(_options: TemplateListFilter): Promise<readonly TemplateItem[]> {
|
||||
// Return only the Hello World template
|
||||
return [
|
||||
{
|
||||
title: 'Hello World',
|
||||
category: 'Getting Started',
|
||||
desc: 'A simple starter project to begin your Noodl journey',
|
||||
iconURL: './assets/template-hello-world-icon.png',
|
||||
projectURL: 'local://hello-world',
|
||||
cloudServicesTemplateURL: undefined
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
canDownload(url: string): Promise<boolean> {
|
||||
// Handle local:// protocol
|
||||
return Promise.resolve(url.startsWith('local://'));
|
||||
}
|
||||
|
||||
async download(url: string, destination: string): Promise<void> {
|
||||
if (url === 'local://hello-world') {
|
||||
// The template is in project-examples folder at the repository root
|
||||
// Use process.cwd() which points to repository root during development
|
||||
const repoRoot = process.cwd();
|
||||
const sourcePath = path.join(repoRoot, 'project-examples', 'version 1.1.0', 'template-project');
|
||||
|
||||
if (!filesystem.exists(sourcePath)) {
|
||||
throw new Error('Hello World template not found at: ' + sourcePath);
|
||||
}
|
||||
|
||||
// Copy the template folder to destination
|
||||
// The destination is expected to be where unzipped content goes
|
||||
// So we copy the folder contents directly
|
||||
FileSystem.instance.copyRecursiveSync(sourcePath, destination);
|
||||
} else {
|
||||
throw new Error(`Unknown local template: ${url}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,14 +59,24 @@ export class NodeGraphTraverser {
|
||||
this.traverseComponent = typeof options.traverseComponent === 'boolean' ? options.traverseComponent : true;
|
||||
this.tagSelector = typeof options.tagSelector === 'function' ? options.tagSelector : null;
|
||||
|
||||
this.root = new TraverseNode(this, null, targetNode || project.getRootNode(), null);
|
||||
const rootNode = targetNode || project.getRootNode();
|
||||
|
||||
// Handle empty projects with no root node
|
||||
if (!rootNode) {
|
||||
this.root = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.root = new TraverseNode(this, null, rootNode, null);
|
||||
}
|
||||
|
||||
public forEach(callback: (node: TraverseNode) => void) {
|
||||
if (!this.root) return;
|
||||
this.root.forEach(callback);
|
||||
}
|
||||
|
||||
public map<T = any>(callback: (node: TraverseNode) => T) {
|
||||
if (!this.root) return [];
|
||||
const items: T[] = [];
|
||||
this.forEach((node) => {
|
||||
const result = callback(node);
|
||||
@@ -76,6 +86,7 @@ export class NodeGraphTraverser {
|
||||
}
|
||||
|
||||
public filter(callback: (node: TraverseNode) => boolean) {
|
||||
if (!this.root) return [];
|
||||
const items: TraverseNode[] = [];
|
||||
this.forEach((node) => {
|
||||
if (callback(node)) items.push(node);
|
||||
|
||||
Reference in New Issue
Block a user