feat: add community publishing and news syndication
Validate and deploy production / validate (push) Successful in 23s
Validate and deploy staging / validate (push) Successful in 50s
Validate and deploy production / deploy (push) Failing after 40s
Validate and deploy staging / deploy (push) Failing after 7s

This commit is contained in:
jb
2026-08-01 21:42:09 -04:00
parent b47e2588e9
commit dd84dff055
288 changed files with 83254 additions and 38351 deletions
+192 -79
View File
@@ -4,47 +4,84 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { randomUUID } from 'node:crypto';
const bundleArgument = process.argv.slice(2).find((argument) => !argument.startsWith('--'));
const bundleArgument = process.argv
.slice(2)
.find((argument) => !argument.startsWith('--'));
if (!bundleArgument || !process.argv.includes('--confirm-merge')) {
console.error('Usage: node scripts/managed-images-import.mjs <bundle.json> --confirm-merge [--update-existing]');
process.exit(2);
console.error(
'Usage: node scripts/managed-images-import.mjs <bundle.json> --confirm-merge [--update-existing]',
);
process.exit(2);
}
const updateExisting = process.argv.includes('--update-existing');
const bundlePath = resolve(bundleArgument);
const bundle = JSON.parse(await readFile(bundlePath, 'utf8'));
if (bundle?.format !== 'shd-build-managed-images' || bundle?.version !== 1 || !Array.isArray(bundle.images)) {
throw new Error('The selected file is not a supported SHD.build managed-image bundle.');
if (
bundle?.format !== 'shd-build-managed-images' ||
bundle?.version !== 1 ||
!Array.isArray(bundle.images)
) {
throw new Error(
'The selected file is not a supported SHD.build managed-image bundle.',
);
}
for (const image of bundle.images) {
if (typeof image?.row?.id !== 'string' || !/^[0-9a-f-]{36}$/i.test(image.row.id)
|| typeof image.row.storage_key !== 'string'
|| !/^(?:library|talent|brand_set|os_protocol|skill_chip|specialization|specialization_ability|specialization_node)-[0-9a-f-]{36}\.webp$/i.test(image.row.storage_key)
|| typeof image.contentBase64 !== 'string') {
throw new Error('The bundle contains an invalid managed image.');
}
const isItemRender = ['weapon_render', 'gear_render'].includes(image.row.source_entity_type);
if (image.row.source_entity_type && (
typeof image.row.source_entity_id !== 'string'
|| !/^[0-9a-f-]{36}$/i.test(image.row.source_entity_id)
|| (isItemRender
? !image.row.storage_key.startsWith('library-')
: !image.row.storage_key.startsWith(`${image.row.source_entity_type}-`))
)) throw new Error('The bundle contains an invalid linked ISAC image.');
if (
typeof image?.row?.id !== 'string' ||
!/^[0-9a-f-]{36}$/i.test(image.row.id) ||
typeof image.row.storage_key !== 'string' ||
!/^(?:library|talent|brand_set|os_protocol|skill_chip|specialization|specialization_ability|specialization_node)-[0-9a-f-]{36}\.webp$/i.test(
image.row.storage_key,
) ||
typeof image.contentBase64 !== 'string'
) {
throw new Error('The bundle contains an invalid managed image.');
}
const isItemRender = ['weapon_render', 'gear_render'].includes(
image.row.source_entity_type,
);
if (
image.row.source_entity_type &&
(typeof image.row.source_entity_id !== 'string' ||
!/^[0-9a-f-]{36}$/i.test(image.row.source_entity_id) ||
(isItemRender
? !image.row.storage_key.startsWith('library-')
: !image.row.storage_key.startsWith(
`${image.row.source_entity_type}-`,
)))
)
throw new Error('The bundle contains an invalid linked ISAC image.');
}
const backup = spawnSync(process.execPath, ['scripts/db-backup.mjs'], {
cwd: process.cwd(), stdio: 'inherit', env: process.env
cwd: process.cwd(),
stdio: 'inherit',
env: process.env,
});
if (backup.error) throw backup.error;
if (backup.status !== 0) throw new Error('Safety backup failed; no managed images were imported.');
const assetBackup = spawnSync(process.execPath, ['scripts/isac-data-export.mjs'], {
cwd: process.cwd(), stdio: 'inherit', env: process.env
});
if (backup.status !== 0)
throw new Error('Safety backup failed; no managed images were imported.');
const assetBackup = spawnSync(
process.execPath,
['scripts/isac-data-export.mjs'],
{
cwd: process.cwd(),
stdio: 'inherit',
env: process.env,
},
);
if (assetBackup.error) throw assetBackup.error;
if (assetBackup.status !== 0) throw new Error('Uploaded-asset safety snapshot failed; no managed images were imported.');
if (assetBackup.status !== 0)
throw new Error(
'Uploaded-asset safety snapshot failed; no managed images were imported.',
);
const databasePath = resolve(process.env.TDR_DATABASE_PATH || './data/shd-build-development.db');
const storageDirectory = resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons');
const databasePath = resolve(
process.env.TDR_DATABASE_PATH || './data/shd-build-development.db',
);
const storageDirectory = resolve(
process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons',
);
const database = await connect(databasePath);
await mkdir(storageDirectory, { recursive: true });
let inserted = 0;
@@ -53,64 +90,140 @@ let unchanged = 0;
const preflight = [];
for (const image of bundle.images) {
const row = image.row;
const existing = await (await database.prepare('SELECT * FROM managed_images WHERE id=?')).get(row.id);
if (existing && !updateExisting) {
const same = ['name','category','tags_json','is_icon','force_white','storage_key','byte_size','width','height','source_entity_type','source_entity_id']
.every((key) => String(existing[key]) === String(row[key]));
if (!same) throw new Error(`Image ${row.id} already exists with different metadata. Re-run with --update-existing after review.`);
unchanged++;
continue;
}
const creator = await (await database.prepare('SELECT id FROM users WHERE id=?')).get(row.created_by);
if (!creator) throw new Error(`Image "${row.name}" references a creator that does not exist in the target database.`);
const nameConflict = await (await database.prepare('SELECT id FROM managed_images WHERE name=? COLLATE NOCASE AND id<>?')).get(row.name, row.id);
if (nameConflict) throw new Error(`Image name "${row.name}" is already assigned to a different target asset.`);
if (row.source_entity_type && row.source_entity_id) {
const sourceConflict = await (await database.prepare(
'SELECT id FROM managed_images WHERE source_entity_type=? AND source_entity_id=? AND id<>?'
)).get(row.source_entity_type, row.source_entity_id, row.id);
if (sourceConflict) throw new Error(`The ${row.source_entity_type} image ${row.source_entity_id} is already registered under a different library asset.`);
}
preflight.push({ image, existing });
const row = image.row;
const existing = await (
await database.prepare('SELECT * FROM managed_images WHERE id=?')
).get(row.id);
if (existing && !updateExisting) {
const same = [
'name',
'category',
'tags_json',
'is_icon',
'force_white',
'storage_key',
'byte_size',
'width',
'height',
'source_entity_type',
'source_entity_id',
].every((key) => String(existing[key]) === String(row[key]));
if (!same)
throw new Error(
`Image ${row.id} already exists with different metadata. Re-run with --update-existing after review.`,
);
unchanged++;
continue;
}
const creator = await (
await database.prepare('SELECT id FROM users WHERE id=?')
).get(row.created_by);
if (!creator)
throw new Error(
`Image "${row.name}" references a creator that does not exist in the target database.`,
);
const nameConflict = await (
await database.prepare(
'SELECT id FROM managed_images WHERE name=? COLLATE NOCASE AND id<>?',
)
).get(row.name, row.id);
if (nameConflict)
throw new Error(
`Image name "${row.name}" is already assigned to a different target asset.`,
);
if (row.source_entity_type && row.source_entity_id) {
const sourceConflict = await (
await database.prepare(
'SELECT id FROM managed_images WHERE source_entity_type=? AND source_entity_id=? AND id<>?',
)
).get(row.source_entity_type, row.source_entity_id, row.id);
if (sourceConflict)
throw new Error(
`The ${row.source_entity_type} image ${row.source_entity_id} is already registered under a different library asset.`,
);
}
preflight.push({ image, existing });
}
await database.exec('BEGIN');
try {
for (const { image, existing } of preflight) {
const row = image.row;
await writeFile(join(storageDirectory, row.storage_key), Buffer.from(image.contentBase64, 'base64'), {
mode: 0o600, flag: existing || row.source_entity_type ? 'w' : 'wx'
});
if (existing) {
await (await database.prepare(`UPDATE managed_images SET
for (const { image, existing } of preflight) {
const row = image.row;
await writeFile(
join(storageDirectory, row.storage_key),
Buffer.from(image.contentBase64, 'base64'),
{
mode: 0o600,
flag: existing || row.source_entity_type ? 'w' : 'wx',
},
);
if (existing) {
await (
await database.prepare(`UPDATE managed_images SET
name=?,category=?,tags_json=?,is_icon=?,force_white=?,storage_key=?,original_filename=?,
byte_size=?,width=?,height=?,source_entity_type=?,source_entity_id=?,updated_at=? WHERE id=?`)).run(
row.name,row.category,row.tags_json,row.is_icon,row.force_white,row.storage_key,row.original_filename,
row.byte_size,row.width,row.height,row.source_entity_type ?? null,row.source_entity_id ?? null,row.updated_at,row.id
);
updated++;
} else {
await (await database.prepare(`INSERT INTO managed_images
byte_size=?,width=?,height=?,source_entity_type=?,source_entity_id=?,updated_at=? WHERE id=?`)
).run(
row.name,
row.category,
row.tags_json,
row.is_icon,
row.force_white,
row.storage_key,
row.original_filename,
row.byte_size,
row.width,
row.height,
row.source_entity_type ?? null,
row.source_entity_id ?? null,
row.updated_at,
row.id,
);
updated++;
} else {
await (
await database.prepare(`INSERT INTO managed_images
(id,name,category,tags_json,is_icon,force_white,storage_key,original_filename,byte_size,width,height,
source_entity_type,source_entity_id,created_by,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)).run(
row.id,row.name,row.category,row.tags_json,row.is_icon,row.force_white,row.storage_key,row.original_filename,
row.byte_size,row.width,row.height,row.source_entity_type ?? null,row.source_entity_id ?? null,
row.created_by,row.created_at,row.updated_at
);
inserted++;
}
await (await database.prepare(`INSERT INTO audit_log
(id,actor_id,entity_type,entity_id,action,before_json,after_json,created_at) VALUES(?,?,?,?,?,?,?,?)`)).run(
randomUUID(), row.created_by, 'system_configuration', row.id,
existing ? 'image.import_updated' : 'image.imported',
existing ? JSON.stringify(existing) : null, JSON.stringify(row), new Date().toISOString()
);
}
await database.exec('COMMIT');
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
).run(
row.id,
row.name,
row.category,
row.tags_json,
row.is_icon,
row.force_white,
row.storage_key,
row.original_filename,
row.byte_size,
row.width,
row.height,
row.source_entity_type ?? null,
row.source_entity_id ?? null,
row.created_by,
row.created_at,
row.updated_at,
);
inserted++;
}
await (
await database.prepare(`INSERT INTO audit_log
(id,actor_id,entity_type,entity_id,action,before_json,after_json,created_at) VALUES(?,?,?,?,?,?,?,?)`)
).run(
randomUUID(),
row.created_by,
'system_configuration',
row.id,
existing ? 'image.import_updated' : 'image.imported',
existing ? JSON.stringify(existing) : null,
JSON.stringify(row),
new Date().toISOString(),
);
}
await database.exec('COMMIT');
} catch (error) {
await database.exec('ROLLBACK');
throw error;
await database.exec('ROLLBACK');
throw error;
}
console.log(`Managed-image merge complete: ${inserted} inserted, ${updated} updated, ${unchanged} unchanged.`);
console.log(
`Managed-image merge complete: ${inserted} inserted, ${updated} updated, ${unchanged} unchanged.`,
);