mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-03-08 01:53:30 +01:00
feat(phase-10): STRUCT-004 project format detection utility
Task: STRUCT-004
Branch: cline-dev-dishant
Cross-branch notes: none -- no shared dependencies with Richard phase 9/6 work
- ProjectFormatDetector class (injectable filesystem, fully testable)
- detect() async + detectSync() sync variants
- Detects legacy (project.json) vs v2 (nodegx.project.json + _registry.json)
- Returns { format, confidence, indicators } for transparent decision-making
- Scoring system: v2 needs score >= 2 to avoid false positives
- Errors in exists() treated as not-found (graceful degradation)
- V2_INDICATORS / LEGACY_INDICATORS sentinel constants exported
- createNodeDetector() factory for Node.js/test contexts
- Convenience methods: getFormat(), isV2(), isLegacy()
- 30 tests in tests/io/ProjectFormatDetector.test.ts
- detect(): 12 cases (all combinations, async fs, error handling)
- detectSync(): 4 cases (incl. throws on async fs)
- Convenience methods: 7 cases
- Constants: 3 cases
- createNodeDetector() integration: 4 cases (real tmp dirs)
- Updated tests/io/index.ts to export detector tests
- Updated PROGRESS-dishant.md: STRUCT-001/002/003/004 all marked complete
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Phase 10A AI-Powered Development: Dishant Progress
|
||||
# Phase 10A — AI-Powered Development: Dishant Progress
|
||||
|
||||
**Developer:** Dishant
|
||||
**Branch:** `cline-dev-dishant`
|
||||
@@ -13,7 +13,7 @@
|
||||
| STRUCT-001 | JSON Schema Definition | COMPLETE | 8 schemas + validator + tests (33/33 pass) |
|
||||
| STRUCT-002 | Export Engine Core | COMPLETE | ProjectExporter + 50 unit tests |
|
||||
| STRUCT-003 | Import Engine Core | COMPLETE | ProjectImporter + 55 unit tests incl. round-trip validation |
|
||||
| STRUCT-004 | Editor Format Detection | TODO | Unblocked by STRUCT-003 |
|
||||
| STRUCT-004 | Editor Format Detection | COMPLETE | ProjectFormatDetector + 30 unit tests incl. integration |
|
||||
| STRUCT-005 | Lazy Loading | TODO | Unblocked by STRUCT-004 |
|
||||
| STRUCT-006 | Save Logic | TODO | Unblocked by STRUCT-005 |
|
||||
| STRUCT-007 | Migration Wizard UI | TODO | Unblocked by STRUCT-006 |
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
---
|
||||
|
||||
## STRUCT-001 JSON Schema Definition COMPLETE
|
||||
## STRUCT-001 — JSON Schema Definition COMPLETE
|
||||
|
||||
**Completed:** 2026-02-18
|
||||
|
||||
@@ -41,20 +41,20 @@ All files under `packages/noodl-editor/src/editor/src/schemas/`:
|
||||
| `styles.schema.json` | `https://opennoodl.dev/schemas/styles-v2.json` | Global styles + variants |
|
||||
| `model.schema.json` | `https://opennoodl.dev/schemas/model-v2.json` | Backend data model definitions |
|
||||
|
||||
- `validator.ts` SchemaValidator singleton, per-schema convenience methods, Ajv v8 + ajv-formats
|
||||
- `index.ts` re-exports all schemas, validator, TypeScript interfaces
|
||||
- `tests/schemas/schema-validator.test.ts` 33 test cases, all passing
|
||||
- `validator.ts` — SchemaValidator singleton, per-schema convenience methods, Ajv v8 + ajv-formats
|
||||
- `index.ts` — re-exports all schemas, validator, TypeScript interfaces
|
||||
- `tests/schemas/schema-validator.test.ts` — 33 test cases, all passing
|
||||
|
||||
### Key decisions
|
||||
|
||||
- `additionalProperties: true` on nodes/connections open-ended by design
|
||||
- Port type is `oneOf [string, object]` Noodl uses both formats
|
||||
- `strict: false` on Ajv schemas use `description` in `definitions`
|
||||
- `require()` for `ajv-formats` avoids TS type conflict
|
||||
- `additionalProperties: true` on nodes/connections — open-ended by design
|
||||
- Port type is `oneOf [string, object]` — Noodl uses both formats
|
||||
- `strict: false` on Ajv — schemas use `description` in `definitions`
|
||||
- `require()` for `ajv-formats` — avoids TS type conflict
|
||||
|
||||
---
|
||||
|
||||
## STRUCT-002 Export Engine Core COMPLETE
|
||||
## STRUCT-002 — Export Engine Core COMPLETE
|
||||
|
||||
**Completed:** 2026-02-19
|
||||
|
||||
@@ -68,7 +68,7 @@ All files under `packages/noodl-editor/src/editor/src/schemas/`:
|
||||
|
||||
### Architecture
|
||||
|
||||
`ProjectExporter` is pure no filesystem access. Takes `LegacyProject`, returns `ExportResult { files[], stats }`.
|
||||
`ProjectExporter` is pure — no filesystem access. Takes `LegacyProject`, returns `ExportResult { files[], stats }`.
|
||||
|
||||
Output layout:
|
||||
```
|
||||
@@ -85,17 +85,17 @@ components/
|
||||
|
||||
### Key decisions
|
||||
|
||||
1. Pure/filesystem-agnostic caller writes files
|
||||
2. Node tree flattening recursive tree to flat array with parent/children IDs
|
||||
3. Styles/routes files conditional only produced when content exists
|
||||
1. Pure/filesystem-agnostic — caller writes files
|
||||
2. Node tree flattening — recursive tree to flat array with parent/children IDs
|
||||
3. Styles/routes files conditional — only produced when content exists
|
||||
4. Legacy `stateParamaters` typo normalised to `stateParameters`
|
||||
5. Metadata split styles/routes extracted, rest stays in project file
|
||||
5. Metadata split — styles/routes extracted, rest stays in project file
|
||||
6. Original legacy path preserved in `component.json` for round-trip fidelity
|
||||
7. Empty `parameters: {}` omitted from output
|
||||
|
||||
---
|
||||
|
||||
## STRUCT-003 Import Engine Core COMPLETE
|
||||
## STRUCT-003 — Import Engine Core COMPLETE
|
||||
|
||||
**Completed:** 2026-02-19
|
||||
|
||||
@@ -108,7 +108,7 @@ components/
|
||||
|
||||
### Architecture
|
||||
|
||||
`ProjectImporter` is pure no filesystem access. Takes `ImportInput` (all file contents), returns `ImportResult { project, warnings }`.
|
||||
`ProjectImporter` is pure — no filesystem access. Takes `ImportInput` (all file contents), returns `ImportResult { project, warnings }`.
|
||||
|
||||
```
|
||||
ImportInput {
|
||||
@@ -129,30 +129,79 @@ ImportInput {
|
||||
|
||||
### Key decisions
|
||||
|
||||
1. Pure/filesystem-agnostic symmetric with ProjectExporter
|
||||
2. `unflattenNodes` two-pass: build map, then wire children in order
|
||||
3. `toLegacyName` prefers `component.path` (preserved original) for perfect round-trip; falls back to reconstruction
|
||||
4. `stateParameters` `stateParamaters` reversal restores legacy typo
|
||||
5. Metadata merge routes/styles merged back into `project.metadata`
|
||||
6. Non-fatal warnings component failures collected, not thrown
|
||||
7. Round-trip validated 20 round-trip tests covering all data types
|
||||
1. Pure/filesystem-agnostic — symmetric with ProjectExporter
|
||||
2. `unflattenNodes` — two-pass: build map, then wire children in order
|
||||
3. `toLegacyName` — prefers `component.path` (preserved original) for perfect round-trip; falls back to reconstruction
|
||||
4. `stateParameters` → `stateParamaters` reversal — restores legacy typo
|
||||
5. Metadata merge — routes/styles merged back into `project.metadata`
|
||||
6. Non-fatal warnings — component failures collected, not thrown
|
||||
7. Round-trip validated — 20 round-trip tests covering all data types
|
||||
|
||||
### Test coverage (55 tests)
|
||||
|
||||
- `unflattenNodes` 11 cases (empty, single, parent-child, deep nesting, order, field restoration)
|
||||
- `toLegacyName` 4 cases (path field, fallback, root, cloud)
|
||||
- `ProjectImporter.import()` 20 cases (basic, metadata, variants, components, nodes)
|
||||
- Round-trip (export import) 20 cases (all data types, edge cases)
|
||||
- `unflattenNodes` — 11 cases (empty, single, parent-child, deep nesting, order, field restoration)
|
||||
- `toLegacyName` — 4 cases (path field, fallback, root, cloud)
|
||||
- `ProjectImporter.import()` — 20 cases (basic, metadata, variants, components, nodes)
|
||||
- Round-trip (export → import) — 20 cases (all data types, edge cases)
|
||||
|
||||
### Cross-branch note for Richard
|
||||
---
|
||||
|
||||
No shared dependencies with Phase 9/6 work. STRUCT-003 is self-contained in `packages/noodl-editor/src/editor/src/io/` and `packages/noodl-editor/tests/io/`. No cherry-pick needed.
|
||||
## STRUCT-004 — Editor Format Detection COMPLETE
|
||||
|
||||
**Completed:** 2026-02-19
|
||||
|
||||
### What was built
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `packages/noodl-editor/src/editor/src/io/ProjectFormatDetector.ts` | Format detection utility |
|
||||
| `packages/noodl-editor/tests/io/ProjectFormatDetector.test.ts` | 30 unit + integration tests |
|
||||
|
||||
### Architecture
|
||||
|
||||
`ProjectFormatDetector` accepts a `DetectorFilesystem` interface (injectable, testable). Supports both async `detect()` and sync `detectSync()`.
|
||||
|
||||
Detection logic (priority order):
|
||||
1. `nodegx.project.json` + `components/_registry.json` → **v2 / high confidence**
|
||||
2. Either v2 indicator alone → **v2 / medium confidence**
|
||||
3. `project.json` only → **legacy / high confidence**
|
||||
4. Nothing found → **unknown / low confidence**
|
||||
5. Both legacy + v2 → **v2 wins** (migration in progress)
|
||||
|
||||
### Key exports
|
||||
|
||||
| Export | Purpose |
|
||||
|--------|---------|
|
||||
| `ProjectFormatDetector` | Main class |
|
||||
| `DetectorFilesystem` | Interface for filesystem injection |
|
||||
| `V2_INDICATORS` | Sentinel filenames for v2 format |
|
||||
| `LEGACY_INDICATORS` | Sentinel filenames for legacy format |
|
||||
| `createNodeDetector()` | Factory using Node.js `fs.existsSync` |
|
||||
| `ProjectFormat` | `'legacy' | 'v2' | 'unknown'` type |
|
||||
| `FormatDetectionResult` | `{ format, confidence, indicators }` |
|
||||
|
||||
### Key decisions
|
||||
|
||||
1. Injectable filesystem — no platform singleton dependency, fully testable
|
||||
2. Supports both sync and async `exists()` — `detectSync()` throws if async fs injected
|
||||
3. `createNodeDetector()` factory for Node.js/test contexts
|
||||
4. Scoring system — v2 needs score ≥ 2 (nodegx.project.json=2, registry=2, components dir=1)
|
||||
5. Errors in `exists()` treated as "not found" — graceful degradation
|
||||
|
||||
### Test coverage (30 tests)
|
||||
|
||||
- `detect()` — 12 cases (all format combinations, async fs, error handling)
|
||||
- `detectSync()` — 4 cases (v2, legacy, unknown, throws on async fs)
|
||||
- Convenience methods — 7 cases (getFormat, isV2, isLegacy)
|
||||
- Constants — 3 cases
|
||||
- `createNodeDetector()` integration — 4 cases (real filesystem with tmp dirs)
|
||||
|
||||
---
|
||||
|
||||
## Decisions and Learnings
|
||||
|
||||
- **[2026-02-18]** Ajv v8 + ajv-formats: use `require()` for ajv-formats to avoid TS type conflict with root-level ajv-formats package
|
||||
- **[2026-02-19]** Legacy `stateParamaters` typo (missing 'e') is real must be preserved in round-trip. Exporter normalises to `stateParameters`, importer reverses it.
|
||||
- **[2026-02-19]** `component.path` field is the key to perfect round-trip fidelity always preserve the original legacy name in the v2 component file
|
||||
- **[2026-02-19]** `unflattenNodes` needs two passes first build the node map, then wire children in order using the children ID array
|
||||
- **[2026-02-19]** Legacy `stateParamaters` typo (missing 'e') is real — must be preserved in round-trip. Exporter normalises to `stateParameters`, importer reverses it.
|
||||
- **[2026-02-19]** `component.path` field is the key to perfect round-trip fidelity — always preserve the original legacy name in the v2 component file
|
||||
- **[2026-02-19]** `unflattenNodes` needs two passes — first build the node map, then wire children in order using the children ID array
|
||||
- **[2026-02-19]** Format detection: inject filesystem interface rather than using platform singleton — makes the utility testable without Electron context and usable in Node.js scripts/tests
|
||||
|
||||
Reference in New Issue
Block a user