mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 15:22:55 +01:00
Fix app startup issues and add TASK-009 template system refactoring
This commit is contained in:
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
|
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.
|
**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).
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
# TASK-009: Template System Refactoring
|
||||||
|
|
||||||
|
**Status**: 📋 Planned
|
||||||
|
**Priority**: Medium
|
||||||
|
**Complexity**: Medium
|
||||||
|
**Estimated Effort**: 2-3 days
|
||||||
|
|
||||||
|
## 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`
|
||||||
|
|
||||||
|
## Related Tasks
|
||||||
|
|
||||||
|
- None yet (this is the first comprehensive template system task)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Created**: January 8, 2026
|
||||||
|
**Last Updated**: January 8, 2026
|
||||||
|
**Assignee**: TBD
|
||||||
@@ -86,6 +86,9 @@ export function NodeGraphContextProvider({ children }: NodeGraphContextProviderP
|
|||||||
if (!nodeGraph) return;
|
if (!nodeGraph) return;
|
||||||
|
|
||||||
function _update(model: ComponentModel) {
|
function _update(model: ComponentModel) {
|
||||||
|
// Guard against undefined model (happens on empty projects)
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
if (isComponentModel_CloudRuntime(model)) {
|
if (isComponentModel_CloudRuntime(model)) {
|
||||||
setActive('backend');
|
setActive('backend');
|
||||||
if (SidebarModel.instance.ActiveId === 'components') {
|
if (SidebarModel.instance.ActiveId === 'components') {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { filesystem } from '@noodl/platform';
|
import { filesystem } from '@noodl/platform';
|
||||||
|
|
||||||
import { bugtracker } from '@noodl-utils/bugtracker';
|
import { bugtracker } from '@noodl-utils/bugtracker';
|
||||||
|
|
||||||
// TODO: Can we merge this with ProjectModules ?
|
// 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';
|
const modulesPath = project._retainedProjectDirectory + '/noodl_modules';
|
||||||
|
|
||||||
|
try {
|
||||||
const files = await filesystem.listDirectory(modulesPath);
|
const files = await filesystem.listDirectory(modulesPath);
|
||||||
|
|
||||||
await Promise.all(
|
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;
|
return modules;
|
||||||
}
|
}
|
||||||
@@ -50,12 +62,14 @@ export async function readProjectModules(project: TSFixme /* ProjectModel */): P
|
|||||||
bugtracker.debug('ProjectModel.readModules');
|
bugtracker.debug('ProjectModel.readModules');
|
||||||
|
|
||||||
const modulesPath = project._retainedProjectDirectory + '/noodl_modules';
|
const modulesPath = project._retainedProjectDirectory + '/noodl_modules';
|
||||||
const files = await filesystem.listDirectory(modulesPath);
|
|
||||||
|
|
||||||
project.modules = [];
|
project.modules = [];
|
||||||
project.previews = [];
|
project.previews = [];
|
||||||
project.componentAnnotations = {};
|
project.componentAnnotations = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const files = await filesystem.listDirectory(modulesPath);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
@@ -84,6 +98,15 @@ export async function readProjectModules(project: TSFixme /* ProjectModel */): P
|
|||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Loaded ${project.modules.length} modules`);
|
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;
|
return project.modules;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,25 +260,51 @@ export class LocalProjectsModel extends Model {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Default template path
|
// Create a minimal Hello World project programmatically
|
||||||
const defaultTemplatePath = './external/projecttemplates/helloworld.zip';
|
// This is a temporary solution until TASK-009-template-system-refactoring is implemented
|
||||||
|
|
||||||
// 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');
|
|
||||||
|
|
||||||
// Create minimal project.json for empty project
|
|
||||||
const minimalProject = {
|
const minimalProject = {
|
||||||
name: name,
|
name: name,
|
||||||
components: [],
|
components: [
|
||||||
settings: {}
|
{
|
||||||
|
name: 'App',
|
||||||
|
ports: [],
|
||||||
|
visual: true,
|
||||||
|
visualStateTransitions: [],
|
||||||
|
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: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
settings: {},
|
||||||
|
metadata: {
|
||||||
|
title: name,
|
||||||
|
description: 'A new Noodl project'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await filesystem.writeFile(filesystem.join(dirEntry, 'project.json'), JSON.stringify(minimalProject, null, 2));
|
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) => {
|
projectFromDirectory(dirEntry, (project) => {
|
||||||
if (!project) {
|
if (!project) {
|
||||||
fn();
|
fn();
|
||||||
@@ -287,9 +313,14 @@ export class LocalProjectsModel extends Model {
|
|||||||
|
|
||||||
project.name = name;
|
project.name = name;
|
||||||
this._addProject(project);
|
this._addProject(project);
|
||||||
|
project.toDirectory(project._retainedProjectDirectory, (res) => {
|
||||||
|
if (res.result === 'success') {
|
||||||
fn(project);
|
fn(project);
|
||||||
});
|
} else {
|
||||||
|
fn();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
|||||||
import { RuntimeType } from '@noodl-models/nodelibrary/NodeLibraryData';
|
import { RuntimeType } from '@noodl-models/nodelibrary/NodeLibraryData';
|
||||||
|
|
||||||
export function getComponentModelRuntimeType(node: ComponentModel) {
|
export function getComponentModelRuntimeType(node: ComponentModel) {
|
||||||
|
// Guard against undefined node (happens on empty projects)
|
||||||
|
if (!node) return RuntimeType.Browser;
|
||||||
|
|
||||||
const name = node.name;
|
const name = node.name;
|
||||||
|
|
||||||
if (name.startsWith('/#__cloud__/')) {
|
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.
|
// Fetch all the Page nodes.
|
||||||
const pages: TraverseNode[] = traverser.filter((node) => node.node.typename === 'Page');
|
const pages: TraverseNode[] = traverser.filter((node) => node.node.typename === 'Page');
|
||||||
|
|
||||||
|
|||||||
@@ -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.traverseComponent = typeof options.traverseComponent === 'boolean' ? options.traverseComponent : true;
|
||||||
this.tagSelector = typeof options.tagSelector === 'function' ? options.tagSelector : null;
|
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) {
|
public forEach(callback: (node: TraverseNode) => void) {
|
||||||
|
if (!this.root) return;
|
||||||
this.root.forEach(callback);
|
this.root.forEach(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public map<T = any>(callback: (node: TraverseNode) => T) {
|
public map<T = any>(callback: (node: TraverseNode) => T) {
|
||||||
|
if (!this.root) return [];
|
||||||
const items: T[] = [];
|
const items: T[] = [];
|
||||||
this.forEach((node) => {
|
this.forEach((node) => {
|
||||||
const result = callback(node);
|
const result = callback(node);
|
||||||
@@ -76,6 +86,7 @@ export class NodeGraphTraverser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public filter(callback: (node: TraverseNode) => boolean) {
|
public filter(callback: (node: TraverseNode) => boolean) {
|
||||||
|
if (!this.root) return [];
|
||||||
const items: TraverseNode[] = [];
|
const items: TraverseNode[] = [];
|
||||||
this.forEach((node) => {
|
this.forEach((node) => {
|
||||||
if (callback(node)) items.push(node);
|
if (callback(node)) items.push(node);
|
||||||
|
|||||||
Reference in New Issue
Block a user