@noego/app 0.0.7 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +11 -4
- package/DEVELOPING.md +73 -0
- package/docs/asset-serving-fix.md +381 -0
- package/package.json +6 -2
- package/scripts/watch-harness.mjs +246 -0
- package/src/args.js +3 -1
- package/src/build/bootstrap.js +107 -5
- package/src/build/html.js +4 -2
- package/src/build/runtime-manifest.js +1 -1
- package/src/build/server.js +14 -4
- package/src/build/ssr.js +130 -13
- package/src/build/ui-common.js +19 -2
- package/src/client.js +14 -2
- package/src/commands/dev.js +239 -40
- package/src/config.js +10 -0
- package/src/runtime/runtime.js +49 -6
- package/test/asset-mounting.test.js +211 -0
- package/test/config-pipeline.test.js +353 -0
- package/test/path-resolution.test.js +164 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* These are the SAME functions used in bootstrap.js
|
|
7
|
+
* We're testing them in isolation with real project data
|
|
8
|
+
*/
|
|
9
|
+
function calculateComponentPaths(config) {
|
|
10
|
+
if (!config.client?.componentDir_abs) {
|
|
11
|
+
return {
|
|
12
|
+
component_dir: '.app/ssr',
|
|
13
|
+
componentDir_abs: '.app/ssr',
|
|
14
|
+
component_suffix: null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const uiRootAbs = config.client.main_abs ? path.dirname(config.client.main_abs) : config.root;
|
|
19
|
+
const relativeFromUiRoot = path.relative(uiRootAbs, config.client.componentDir_abs);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
component_dir: path.join('.app/ssr', relativeFromUiRoot),
|
|
23
|
+
componentDir_abs: path.join('.app/ssr', relativeFromUiRoot),
|
|
24
|
+
component_suffix: relativeFromUiRoot || null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function calculateAssetFallback(componentSuffix) {
|
|
29
|
+
if (!componentSuffix) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return path.join('.app/assets', componentSuffix);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Test with REAL project configurations
|
|
37
|
+
*/
|
|
38
|
+
test('markdown_view: componentDir in subfolder', () => {
|
|
39
|
+
// Actual values from markdown_view/hammer.config.yml
|
|
40
|
+
const config = {
|
|
41
|
+
root: '/Users/shavauhngabay/dev/markdown_view',
|
|
42
|
+
client: {
|
|
43
|
+
main_abs: '/Users/shavauhngabay/dev/markdown_view/frontend/frontend.ts',
|
|
44
|
+
componentDir_abs: '/Users/shavauhngabay/dev/markdown_view/frontend/components'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = calculateComponentPaths(config);
|
|
49
|
+
const assetFallback = calculateAssetFallback(result.component_suffix);
|
|
50
|
+
|
|
51
|
+
console.log('markdown_view result:', result);
|
|
52
|
+
console.log('markdown_view asset fallback:', assetFallback);
|
|
53
|
+
|
|
54
|
+
// Expected: components are nested in 'components' subfolder
|
|
55
|
+
assert.strictEqual(result.component_dir, '.app/ssr/components', 'component_dir should be .app/ssr/components');
|
|
56
|
+
assert.strictEqual(result.component_suffix, 'components', 'component_suffix should be components');
|
|
57
|
+
assert.strictEqual(assetFallback, '.app/assets/components', 'asset fallback should be .app/assets/components');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('noblelaw/liftlog: no explicit componentDir', () => {
|
|
61
|
+
// When componentDir is not explicitly set, it defaults to UI root
|
|
62
|
+
const config = {
|
|
63
|
+
root: '/Users/shavauhngabay/dev/noblelaw',
|
|
64
|
+
client: {
|
|
65
|
+
main_abs: '/Users/shavauhngabay/dev/noblelaw/ui/main.ts',
|
|
66
|
+
componentDir_abs: null
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const result = calculateComponentPaths(config);
|
|
71
|
+
const assetFallback = calculateAssetFallback(result.component_suffix);
|
|
72
|
+
|
|
73
|
+
console.log('noblelaw result:', result);
|
|
74
|
+
console.log('noblelaw asset fallback:', assetFallback);
|
|
75
|
+
|
|
76
|
+
// Expected: no suffix, components at root
|
|
77
|
+
assert.strictEqual(result.component_dir, '.app/ssr', 'component_dir should be .app/ssr');
|
|
78
|
+
assert.strictEqual(result.component_suffix, null, 'component_suffix should be null');
|
|
79
|
+
assert.strictEqual(assetFallback, null, 'asset fallback should be null');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('deeply nested componentDir', () => {
|
|
83
|
+
// Edge case: deeply nested component directory
|
|
84
|
+
const config = {
|
|
85
|
+
root: '/Users/test/project',
|
|
86
|
+
client: {
|
|
87
|
+
main_abs: '/Users/test/project/src/ui/main.ts',
|
|
88
|
+
componentDir_abs: '/Users/test/project/src/ui/components/shared'
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const result = calculateComponentPaths(config);
|
|
93
|
+
const assetFallback = calculateAssetFallback(result.component_suffix);
|
|
94
|
+
|
|
95
|
+
console.log('nested result:', result);
|
|
96
|
+
console.log('nested asset fallback:', assetFallback);
|
|
97
|
+
|
|
98
|
+
// Expected: preserves nested structure
|
|
99
|
+
assert.strictEqual(result.component_dir, '.app/ssr/components/shared', 'component_dir should preserve nested path');
|
|
100
|
+
assert.strictEqual(result.component_suffix, 'components/shared', 'component_suffix should be components/shared');
|
|
101
|
+
assert.strictEqual(assetFallback, '.app/assets/components/shared', 'asset fallback should match nested structure');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Test the ACTUAL bug: verify that when we pass component_dir to client.js,
|
|
106
|
+
* we can extract the suffix without complex string manipulation
|
|
107
|
+
*/
|
|
108
|
+
test('verify simple suffix extraction', () => {
|
|
109
|
+
const testCases = [
|
|
110
|
+
{ component_dir: '.app/ssr/components', expected: 'components' },
|
|
111
|
+
{ component_dir: '.app/ssr', expected: null },
|
|
112
|
+
{ component_dir: '.app/ssr/components/shared', expected: 'components/shared' }
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
testCases.forEach(({ component_dir, expected }) => {
|
|
116
|
+
// This is what we should store in the config
|
|
117
|
+
const suffix = component_dir.replace('.app/ssr', '').replace(/^\//, '') || null;
|
|
118
|
+
|
|
119
|
+
console.log(`component_dir: ${component_dir} → suffix: ${suffix}`);
|
|
120
|
+
|
|
121
|
+
assert.strictEqual(
|
|
122
|
+
suffix,
|
|
123
|
+
expected,
|
|
124
|
+
`component_dir ${component_dir} should extract suffix ${expected}`
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Integration test: Full pipeline from config → bootstrap → runtime
|
|
131
|
+
*/
|
|
132
|
+
test('full pipeline: markdown_view', () => {
|
|
133
|
+
// Step 1: Config input
|
|
134
|
+
const configInput = {
|
|
135
|
+
root: '/Users/shavauhngabay/dev/markdown_view',
|
|
136
|
+
client: {
|
|
137
|
+
main_abs: '/Users/shavauhngabay/dev/markdown_view/frontend/frontend.ts',
|
|
138
|
+
componentDir_abs: '/Users/shavauhngabay/dev/markdown_view/frontend/components'
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Step 2: Bootstrap calculation
|
|
143
|
+
const bootstrapResult = calculateComponentPaths(configInput);
|
|
144
|
+
|
|
145
|
+
// Step 3: Runtime usage (what client.js needs)
|
|
146
|
+
const runtimeAssetFallback = calculateAssetFallback(bootstrapResult.component_suffix);
|
|
147
|
+
|
|
148
|
+
console.log('=== Full Pipeline Test ===');
|
|
149
|
+
console.log('Config input:', configInput.client);
|
|
150
|
+
console.log('Bootstrap result:', bootstrapResult);
|
|
151
|
+
console.log('Runtime asset fallback:', runtimeAssetFallback);
|
|
152
|
+
|
|
153
|
+
// Verify the entire flow
|
|
154
|
+
assert.strictEqual(bootstrapResult.component_dir, '.app/ssr/components');
|
|
155
|
+
assert.strictEqual(bootstrapResult.component_suffix, 'components');
|
|
156
|
+
assert.strictEqual(runtimeAssetFallback, '.app/assets/components');
|
|
157
|
+
|
|
158
|
+
// This is what we need to mount in Express
|
|
159
|
+
console.log('\n✅ Express should mount:');
|
|
160
|
+
console.log(' Primary: .app/assets at /assets');
|
|
161
|
+
console.log(` Fallback: ${runtimeAssetFallback} at /assets`);
|
|
162
|
+
console.log('\n✅ This allows browser to request: /assets/layout/root.js');
|
|
163
|
+
console.log(` And Express finds it at: ${runtimeAssetFallback}/layout/root.js`);
|
|
164
|
+
});
|