fix(deploy-to-hub): keep internal subfolder paths identity-mapped when bundling

This commit is contained in:
tristantr
2026-06-10 11:59:37 +02:00
parent 29c888198f
commit f4c794f1d6
2 changed files with 30 additions and 5 deletions
@@ -107,6 +107,21 @@ describe('buildPathMap', () => {
expect(m.get('u/alice/db')).toBe('f/proj/db_2')
expect(m.get('u/bob/db')).toBe('f/proj/db_3')
})
it('maps internal paths to themselves, preserving subfolder depth', () => {
const m = buildPathMap(['f/proj/api', 'f/proj/sub/deep/script'], 'proj')
expect(m.get('f/proj/api')).toBe('f/proj/api')
expect(m.get('f/proj/sub/deep/script')).toBe('f/proj/sub/deep/script')
})
it('does not flatten two internal items sharing a leaf name', () => {
const m = buildPathMap(['f/proj/a/x', 'f/proj/b/x'], 'proj')
expect(m.get('f/proj/a/x')).toBe('f/proj/a/x')
expect(m.get('f/proj/b/x')).toBe('f/proj/b/x')
})
it('relocates an external onto a suffix when its leaf collides with an internal path', () => {
const m = buildPathMap(['f/proj/db', 'u/admin/db'], 'proj')
expect(m.get('f/proj/db')).toBe('f/proj/db')
expect(m.get('u/admin/db')).toBe('f/proj/db_2')
})
})
describe('rewriteContent', () => {
@@ -99,16 +99,26 @@ export function extractAppRefs(value: any): Ref[] {
}
/**
* Build the relocation map for external paths. Each `u/<user>/<name>` or
* `f/<other>/<name>` maps to `f/<slug>/<name>`; on collision the later entry
* gets a `_2`, `_3`… suffix. Input is sorted first so the suffix assignment is
* Build the relocation map. Paths already inside the project folder
* (`f/<slug>/...`) map to themselves — their structure and subfolder depth are
* preserved. Only external paths (`u/<user>/<name>` or `f/<other>/<name>`) are
* relocated to `f/<slug>/<name>`; on collision the later entry gets a `_2`,
* `_3`… suffix. Internal paths are reserved first so an external one can never
* land on an occupied internal path. Input is sorted so suffix assignment is
* deterministic regardless of discovery order.
*/
export function buildPathMap(externalPaths: Iterable<string>, slug: string): Map<string, string> {
export function buildPathMap(paths: Iterable<string>, slug: string): Map<string, string> {
const map = new Map<string, string>()
const used = new Set<string>()
const sorted = [...new Set(externalPaths)].sort()
const sorted = [...new Set(paths)].sort()
for (const p of sorted) {
if (classifyPath(p, slug) === 'internal') {
map.set(p, p)
used.add(p)
}
}
for (const old of sorted) {
if (map.has(old)) continue
const name = old.split('/').filter(Boolean).pop() ?? old
let candidate = `f/${slug}/${name}`
let n = 2