chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* `file-upload` subblock remapping for fork/promote.
|
||||
*
|
||||
* A `file-upload` value is a workspace-file reference (or array of them) stored as
|
||||
* objects `{ key, name, ... }` where `key` is the object-storage key (NOT the
|
||||
* `workspace_files.id`). Forking copies the blob to a new key; this rewrites each
|
||||
* reference's key to the copied key, preserving the rest of the object. References
|
||||
* whose file was not copied are dropped (the field is emptied) rather than left
|
||||
* pointing at another workspace's blob. External `file-selector` references
|
||||
* (provider file ids, credential-scoped) are NOT handled here - they carry over
|
||||
* unchanged.
|
||||
*/
|
||||
|
||||
function parseMaybeJson(value: unknown): { value: unknown; serialized: boolean } {
|
||||
if (typeof value !== 'string') return { value, serialized: false }
|
||||
const trimmed = value.trim()
|
||||
const looksJson =
|
||||
(trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
||||
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
||||
if (!looksJson) return { value, serialized: false }
|
||||
try {
|
||||
return { value: JSON.parse(trimmed), serialized: true }
|
||||
} catch {
|
||||
return { value, serialized: false }
|
||||
}
|
||||
}
|
||||
|
||||
/** The field a file-upload item uses as its storage key, and that key's value. */
|
||||
function fileItemKeyField(item: unknown): { field: 'key' | 'path' | 'name'; key: string } | null {
|
||||
if (!item || typeof item !== 'object' || Array.isArray(item)) return null
|
||||
const record = item as Record<string, unknown>
|
||||
for (const field of ['key', 'path', 'name'] as const) {
|
||||
const value = record[field]
|
||||
if (typeof value === 'string' && value.trim().length > 0) return { field, key: value }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the workspace-file storage keys referenced by a `file-upload` subblock
|
||||
* value (single object, array, or JSON-string form). Used at promote time to emit each
|
||||
* workspace file as a `file` reference (keyed by storage key) so it surfaces in the
|
||||
* scan / unmapped set and can be copied into the target. Deduplicated, order-preserving.
|
||||
*/
|
||||
export function collectForkFileUploadKeys(value: unknown): string[] {
|
||||
const parsed = parseMaybeJson(value)
|
||||
const items = Array.isArray(parsed.value)
|
||||
? (parsed.value as unknown[])
|
||||
: parsed.value
|
||||
? [parsed.value]
|
||||
: []
|
||||
const keys: string[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const item of items) {
|
||||
const keyInfo = fileItemKeyField(item)
|
||||
if (!keyInfo || seen.has(keyInfo.key)) continue
|
||||
seen.add(keyInfo.key)
|
||||
keys.push(keyInfo.key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
/**
|
||||
* Remap a `file-upload` subblock value. `resolveFileKey(sourceKey)` returns the
|
||||
* copied target storage key, or null when the file was not copied (drop the ref).
|
||||
*/
|
||||
export function remapForkFileUploadValue(
|
||||
value: unknown,
|
||||
resolveFileKey: (sourceKey: string) => string | null
|
||||
): unknown {
|
||||
const parsed = parseMaybeJson(value)
|
||||
const isArray = Array.isArray(parsed.value)
|
||||
const items = isArray ? (parsed.value as unknown[]) : parsed.value ? [parsed.value] : []
|
||||
if (items.length === 0) return value
|
||||
|
||||
const next: unknown[] = []
|
||||
let changed = false
|
||||
for (const item of items) {
|
||||
const keyInfo = fileItemKeyField(item)
|
||||
if (!keyInfo) {
|
||||
next.push(item)
|
||||
continue
|
||||
}
|
||||
const targetKey = resolveFileKey(keyInfo.key)
|
||||
if (targetKey == null) {
|
||||
changed = true
|
||||
continue
|
||||
}
|
||||
if (targetKey === keyInfo.key) {
|
||||
next.push(item)
|
||||
continue
|
||||
}
|
||||
changed = true
|
||||
next.push({ ...(item as Record<string, unknown>), [keyInfo.field]: targetKey })
|
||||
}
|
||||
|
||||
if (!changed) return value
|
||||
if (next.length === 0) return ''
|
||||
if (isArray) return parsed.serialized ? JSON.stringify(next) : next
|
||||
return parsed.serialized ? JSON.stringify(next[0]) : next[0]
|
||||
}
|
||||
Reference in New Issue
Block a user