@mean-weasel/lineage 0.1.2 → 0.1.3
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/CHANGELOG.md +6 -0
- package/README.md +46 -1
- package/dist/cli/lineage-dev.js +2 -2
- package/dist/cli/lineage-dev.js.map +2 -2
- package/dist/cli/lineage.js +2 -2
- package/dist/cli/lineage.js.map +2 -2
- package/dist/server.js +410 -21
- package/dist/server.js.map +3 -3
- package/dist/web/assets/index-CTsKYXYF.js +19 -0
- package/dist/web/index.html +1 -1
- package/fixtures/demo-project/lineage/swissifier-rich-demo.json +194 -0
- package/package.json +1 -1
- package/dist/web/assets/index-Cm95o4aV.js +0 -19
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
2
|
import express2 from "express";
|
|
3
3
|
import multer from "multer";
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
5
5
|
import { join as join9 } from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/server/assetCore.ts
|
|
@@ -4527,11 +4527,14 @@ function listImageGenerationJobs(project = defaultProject, fields = {}) {
|
|
|
4527
4527
|
|
|
4528
4528
|
// src/server/assetLineageDemo.ts
|
|
4529
4529
|
import { createHash as createHash2 } from "node:crypto";
|
|
4530
|
-
import { mkdirSync as mkdirSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4531
|
-
import {
|
|
4530
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4531
|
+
import { tmpdir } from "node:os";
|
|
4532
|
+
import { dirname as dirname3, join as join8, posix } from "node:path";
|
|
4533
|
+
import { gunzipSync } from "node:zlib";
|
|
4532
4534
|
var demoWorkspaceTitle = "Demo: Content iteration tree";
|
|
4533
4535
|
var demoWorkspaceNotes = "Repeatable sample lineage for demos and onboarding. Archive it when reviewing real work.";
|
|
4534
4536
|
var demoBasePath = ["lineage-demo", "2026-06-lineage-demo"];
|
|
4537
|
+
var swissifierManifestPath = join8(repoRoot, "fixtures", defaultProject, "lineage", "swissifier-rich-demo.json");
|
|
4535
4538
|
var demoAssets = [
|
|
4536
4539
|
{ key: "root", channel: "linkedin", file: "demo-root.svg", label: "Initial Demo Concept", fill: "#f6fbfb", stroke: "#0b7f88" },
|
|
4537
4540
|
{ key: "hookA", channel: "tiktok", file: "demo-hook-a-v01.svg", label: "Hook A v01", fill: "#fff8e6", stroke: "#9a6a00" },
|
|
@@ -4573,25 +4576,288 @@ function demoProjectDir(project) {
|
|
|
4573
4576
|
function demoRelativePath(project, asset) {
|
|
4574
4577
|
return join8(...demoBasePath, project, asset.channel, asset.file);
|
|
4575
4578
|
}
|
|
4576
|
-
function
|
|
4579
|
+
function demoFilePath(project, asset) {
|
|
4580
|
+
return join8(demoProjectDir(project), asset.channel, asset.file);
|
|
4581
|
+
}
|
|
4582
|
+
function swissifierManifest() {
|
|
4583
|
+
return JSON.parse(readFileSync4(swissifierManifestPath, "utf8"));
|
|
4584
|
+
}
|
|
4585
|
+
function swissifierRelativePath(manifest, asset) {
|
|
4586
|
+
return join8(manifest.media.target_dir, asset.file);
|
|
4587
|
+
}
|
|
4588
|
+
function swissifierFilePath(manifest, asset) {
|
|
4589
|
+
return join8(repoRoot, ".asset-scratch", swissifierRelativePath(manifest, asset));
|
|
4590
|
+
}
|
|
4591
|
+
function swissifierSourcePath(manifest, asset, sourceDir) {
|
|
4592
|
+
const direct = join8(sourceDir, asset.file);
|
|
4593
|
+
if (existsSync5(direct)) return direct;
|
|
4594
|
+
const nested = join8(sourceDir, manifest.media.target_dir, asset.file);
|
|
4595
|
+
return existsSync5(nested) ? nested : null;
|
|
4596
|
+
}
|
|
4597
|
+
function swissifierMediaState(manifest) {
|
|
4598
|
+
const missing = [];
|
|
4599
|
+
const invalid = [];
|
|
4600
|
+
for (const asset of manifest.assets) {
|
|
4601
|
+
const relativePath = swissifierRelativePath(manifest, asset);
|
|
4602
|
+
const path = swissifierFilePath(manifest, asset);
|
|
4603
|
+
if (!existsSync5(path)) {
|
|
4604
|
+
missing.push(relativePath);
|
|
4605
|
+
continue;
|
|
4606
|
+
}
|
|
4607
|
+
if (fileSha256(path) !== asset.checksum_sha256) invalid.push(relativePath);
|
|
4608
|
+
}
|
|
4609
|
+
return { invalid, missing };
|
|
4610
|
+
}
|
|
4611
|
+
function sha256Hex(input) {
|
|
4612
|
+
return createHash2("sha256").update(input).digest("hex");
|
|
4613
|
+
}
|
|
4614
|
+
function safeTarEntryName(rawName) {
|
|
4615
|
+
const stripped = rawName.replace(/\0.*$/, "").replace(/^\.\//, "");
|
|
4616
|
+
if (!stripped || stripped === ".") return "";
|
|
4617
|
+
if (stripped.includes("\\") || stripped.startsWith("/") || /^[A-Za-z]:/.test(stripped)) {
|
|
4618
|
+
throw new Error(`Unsafe Swissifier media archive path: ${rawName}`);
|
|
4619
|
+
}
|
|
4620
|
+
const normalized = posix.normalize(stripped);
|
|
4621
|
+
if (!normalized || normalized === "." || normalized.startsWith("../") || normalized === "..") {
|
|
4622
|
+
throw new Error(`Unsafe Swissifier media archive path: ${rawName}`);
|
|
4623
|
+
}
|
|
4624
|
+
return normalized;
|
|
4625
|
+
}
|
|
4626
|
+
function tarOctal(header, start, length) {
|
|
4627
|
+
const raw = header.toString("utf8", start, start + length).replace(/\0.*$/, "").trim();
|
|
4628
|
+
if (!raw) return 0;
|
|
4629
|
+
if (!/^[0-7]+$/.test(raw)) throw new Error(`Invalid Swissifier media archive size: ${raw}`);
|
|
4630
|
+
return Number.parseInt(raw, 8);
|
|
4631
|
+
}
|
|
4632
|
+
function tarName(header) {
|
|
4633
|
+
const name = header.toString("utf8", 0, 100).replace(/\0.*$/, "");
|
|
4634
|
+
const prefix = header.toString("utf8", 345, 500).replace(/\0.*$/, "");
|
|
4635
|
+
return prefix ? `${prefix}/${name}` : name;
|
|
4636
|
+
}
|
|
4637
|
+
function isAppleDoubleEntry(name) {
|
|
4638
|
+
return name === "._." || name.startsWith("._") || name.includes("/._");
|
|
4639
|
+
}
|
|
4640
|
+
function extractSwissifierMediaArchive(archive, manifest, destination) {
|
|
4641
|
+
const expected = new Map(manifest.assets.map((asset) => [asset.file, asset]));
|
|
4642
|
+
const extracted = /* @__PURE__ */ new Set();
|
|
4643
|
+
const body = gunzipSync(archive);
|
|
4644
|
+
let offset = 0;
|
|
4645
|
+
while (offset + 512 <= body.length) {
|
|
4646
|
+
const header = body.subarray(offset, offset + 512);
|
|
4647
|
+
offset += 512;
|
|
4648
|
+
if (header.every((byte) => byte === 0)) break;
|
|
4649
|
+
const typeflag = header.toString("utf8", 156, 157);
|
|
4650
|
+
const rawName = tarName(header);
|
|
4651
|
+
const name = safeTarEntryName(rawName);
|
|
4652
|
+
const size = tarOctal(header, 124, 12);
|
|
4653
|
+
const nextOffset = offset + Math.ceil(size / 512) * 512;
|
|
4654
|
+
if (nextOffset > body.length) throw new Error(`Truncated Swissifier media archive entry: ${rawName}`);
|
|
4655
|
+
if (!name || typeflag === "5") {
|
|
4656
|
+
offset = nextOffset;
|
|
4657
|
+
continue;
|
|
4658
|
+
}
|
|
4659
|
+
if (typeflag === "x" || typeflag === "g") {
|
|
4660
|
+
offset = nextOffset;
|
|
4661
|
+
continue;
|
|
4662
|
+
}
|
|
4663
|
+
if (isAppleDoubleEntry(name)) {
|
|
4664
|
+
offset = nextOffset;
|
|
4665
|
+
continue;
|
|
4666
|
+
}
|
|
4667
|
+
if (typeflag && typeflag !== "0") throw new Error(`Unsupported Swissifier media archive entry type for ${name}`);
|
|
4668
|
+
const directName = name.startsWith(`${manifest.media.target_dir}/`) ? name.slice(manifest.media.target_dir.length + 1) : name;
|
|
4669
|
+
const asset = expected.get(directName);
|
|
4670
|
+
if (!asset) throw new Error(`Unexpected Swissifier media archive entry: ${name}`);
|
|
4671
|
+
if (size !== asset.size_bytes) throw new Error(`Unexpected Swissifier media archive size for ${name}`);
|
|
4672
|
+
const file = body.subarray(offset, offset + size);
|
|
4673
|
+
const actualSha = sha256Hex(file);
|
|
4674
|
+
if (actualSha !== asset.checksum_sha256) throw new Error(`Checksum mismatch for Swissifier media archive entry: ${name}`);
|
|
4675
|
+
const target = join8(destination, directName);
|
|
4676
|
+
mkdirSync5(dirname3(target), { recursive: true });
|
|
4677
|
+
writeFileSync3(target, file);
|
|
4678
|
+
extracted.add(directName);
|
|
4679
|
+
offset = nextOffset;
|
|
4680
|
+
}
|
|
4681
|
+
const missing = manifest.assets.map((asset) => asset.file).filter((file) => !extracted.has(file));
|
|
4682
|
+
if (missing.length) throw new Error(`Swissifier media archive missing ${missing.length} expected file${missing.length === 1 ? "" : "s"}`);
|
|
4683
|
+
return { extracted: extracted.size };
|
|
4684
|
+
}
|
|
4685
|
+
async function downloadBuffer(url, maxBytes) {
|
|
4686
|
+
const response = await fetch(url);
|
|
4687
|
+
if (!response.ok) throw new Error(`Swissifier media download failed with HTTP ${response.status}`);
|
|
4688
|
+
const contentLength = Number(response.headers.get("content-length") || 0);
|
|
4689
|
+
if (contentLength > maxBytes) throw new Error(`Swissifier media download is too large: ${contentLength} bytes`);
|
|
4690
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
4691
|
+
if (buffer.length > maxBytes) throw new Error(`Swissifier media download is too large: ${buffer.length} bytes`);
|
|
4692
|
+
return buffer;
|
|
4693
|
+
}
|
|
4694
|
+
function missingDemoMedia(project) {
|
|
4695
|
+
return demoAssets.filter((asset) => !existsSync5(demoFilePath(project, asset))).map((asset) => demoRelativePath(project, asset));
|
|
4696
|
+
}
|
|
4697
|
+
function demoSeedMediaStatus(project = defaultProject) {
|
|
4698
|
+
const missing = missingDemoMedia(project);
|
|
4699
|
+
const present = demoAssets.length - missing.length;
|
|
4577
4700
|
return {
|
|
4578
4701
|
ok: true,
|
|
4579
4702
|
media_root: join8(repoRoot, ".asset-scratch"),
|
|
4580
|
-
present
|
|
4703
|
+
present,
|
|
4581
4704
|
total: demoAssets.length,
|
|
4582
|
-
missing
|
|
4583
|
-
fixture_present:
|
|
4705
|
+
missing,
|
|
4706
|
+
fixture_present: present,
|
|
4584
4707
|
fixture_total: demoAssets.length,
|
|
4585
|
-
fixture_missing:
|
|
4708
|
+
fixture_missing: missing
|
|
4586
4709
|
};
|
|
4587
4710
|
}
|
|
4588
|
-
function
|
|
4711
|
+
function swissifierRichDemoMediaStatus(project = defaultProject) {
|
|
4712
|
+
const manifest = swissifierManifest();
|
|
4713
|
+
const { invalid, missing } = swissifierMediaState(manifest);
|
|
4714
|
+
const present = manifest.assets.length - missing.length - invalid.length;
|
|
4715
|
+
return {
|
|
4716
|
+
ok: true,
|
|
4717
|
+
demo_id: manifest.id,
|
|
4718
|
+
project,
|
|
4719
|
+
media_root: join8(repoRoot, ".asset-scratch"),
|
|
4720
|
+
media_target: manifest.media.target_dir,
|
|
4721
|
+
download_available: Boolean(manifest.media.download),
|
|
4722
|
+
download_file: manifest.media.download?.file,
|
|
4723
|
+
download_sha256: manifest.media.download?.sha256,
|
|
4724
|
+
download_url: manifest.media.download?.url,
|
|
4725
|
+
source_env: manifest.media.source_env,
|
|
4726
|
+
source_hint: manifest.media.source_hint,
|
|
4727
|
+
present,
|
|
4728
|
+
total: manifest.assets.length,
|
|
4729
|
+
missing,
|
|
4730
|
+
invalid,
|
|
4731
|
+
fixture_present: present,
|
|
4732
|
+
fixture_total: manifest.assets.length,
|
|
4733
|
+
fixture_missing: [...missing, ...invalid]
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
async function downloadSwissifierRichDemoMedia(project = defaultProject, fields = { confirmWrite: false }) {
|
|
4737
|
+
const manifest = swissifierManifest();
|
|
4738
|
+
const download = manifest.media.download;
|
|
4739
|
+
if (!download) {
|
|
4740
|
+
return {
|
|
4741
|
+
ok: true,
|
|
4742
|
+
demo_id: manifest.id,
|
|
4743
|
+
project,
|
|
4744
|
+
download_available: false,
|
|
4745
|
+
restored: 0,
|
|
4746
|
+
total: manifest.assets.length
|
|
4747
|
+
};
|
|
4748
|
+
}
|
|
4749
|
+
const sourceUrl = fields.sourceUrl || download.url;
|
|
4750
|
+
const expectedSha256 = fields.expectedSha256 || download.sha256;
|
|
4751
|
+
const maxBytes = Math.max(download.size_bytes + 1024 * 1024, download.size_bytes * 1.1);
|
|
4752
|
+
const archive = await downloadBuffer(sourceUrl, maxBytes);
|
|
4753
|
+
const archiveSha256 = sha256Hex(archive);
|
|
4754
|
+
if (archiveSha256 !== expectedSha256) throw new Error(`Swissifier media download checksum mismatch: expected ${expectedSha256}, got ${archiveSha256}`);
|
|
4755
|
+
if (!fields.confirmWrite) {
|
|
4756
|
+
return {
|
|
4757
|
+
ok: true,
|
|
4758
|
+
demo_id: manifest.id,
|
|
4759
|
+
project,
|
|
4760
|
+
dryRun: true,
|
|
4761
|
+
download_available: true,
|
|
4762
|
+
download_file: download.file,
|
|
4763
|
+
download_url: sourceUrl,
|
|
4764
|
+
archive_sha256: archiveSha256,
|
|
4765
|
+
restored: 0,
|
|
4766
|
+
total: manifest.assets.length,
|
|
4767
|
+
would_restore: manifest.assets.length
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4770
|
+
const sourceDir = mkdtempSync(join8(tmpdir(), "lineage-swissifier-media-"));
|
|
4771
|
+
try {
|
|
4772
|
+
const extracted = extractSwissifierMediaArchive(archive, manifest, sourceDir);
|
|
4773
|
+
const restored = restoreSwissifierRichDemoMedia(project, { confirmWrite: true, sourceDir });
|
|
4774
|
+
return {
|
|
4775
|
+
...restored,
|
|
4776
|
+
download_available: true,
|
|
4777
|
+
download_file: download.file,
|
|
4778
|
+
download_url: sourceUrl,
|
|
4779
|
+
archive_sha256: archiveSha256,
|
|
4780
|
+
extracted: extracted.extracted,
|
|
4781
|
+
media_status: swissifierRichDemoMediaStatus(project)
|
|
4782
|
+
};
|
|
4783
|
+
} finally {
|
|
4784
|
+
rmSync(sourceDir, { force: true, recursive: true });
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
function restoreDemoSeedMedia(project = defaultProject, fields = { confirmWrite: false }) {
|
|
4788
|
+
const missing = new Set(missingDemoMedia(project));
|
|
4789
|
+
if (fields.confirmWrite) {
|
|
4790
|
+
for (const asset of demoAssets) {
|
|
4791
|
+
if (missing.has(demoRelativePath(project, asset))) writeDemoFile(project, asset);
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4589
4794
|
return {
|
|
4590
4795
|
ok: true,
|
|
4591
4796
|
dryRun: !fields.confirmWrite,
|
|
4592
4797
|
media_root: join8(repoRoot, ".asset-scratch"),
|
|
4593
|
-
restored: 0,
|
|
4594
|
-
total: demoAssets.length
|
|
4798
|
+
restored: fields.confirmWrite ? missing.size : 0,
|
|
4799
|
+
total: demoAssets.length,
|
|
4800
|
+
would_restore: fields.confirmWrite ? 0 : missing.size,
|
|
4801
|
+
missing: Array.from(missing)
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4804
|
+
function restoreSwissifierRichDemoMedia(project = defaultProject, fields = { confirmWrite: false }) {
|
|
4805
|
+
const manifest = swissifierManifest();
|
|
4806
|
+
const before = swissifierMediaState(manifest);
|
|
4807
|
+
const sourceDir = fields.sourceDir || process.env[manifest.media.source_env];
|
|
4808
|
+
if (!sourceDir) {
|
|
4809
|
+
return {
|
|
4810
|
+
ok: true,
|
|
4811
|
+
demo_id: manifest.id,
|
|
4812
|
+
project,
|
|
4813
|
+
dryRun: !fields.confirmWrite,
|
|
4814
|
+
media_root: join8(repoRoot, ".asset-scratch"),
|
|
4815
|
+
restored: 0,
|
|
4816
|
+
total: manifest.assets.length,
|
|
4817
|
+
would_restore: 0,
|
|
4818
|
+
source_required: true,
|
|
4819
|
+
source_env: manifest.media.source_env,
|
|
4820
|
+
source_hint: manifest.media.source_hint,
|
|
4821
|
+
missing: before.missing,
|
|
4822
|
+
invalid: before.invalid,
|
|
4823
|
+
unavailable: [...before.missing, ...before.invalid]
|
|
4824
|
+
};
|
|
4825
|
+
}
|
|
4826
|
+
const unavailable = [];
|
|
4827
|
+
const copyable = [];
|
|
4828
|
+
const wanted = /* @__PURE__ */ new Set([...before.missing, ...before.invalid]);
|
|
4829
|
+
for (const asset of manifest.assets) {
|
|
4830
|
+
const relativePath = swissifierRelativePath(manifest, asset);
|
|
4831
|
+
if (!wanted.has(relativePath)) continue;
|
|
4832
|
+
const source = swissifierSourcePath(manifest, asset, sourceDir);
|
|
4833
|
+
if (!source || fileSha256(source) !== asset.checksum_sha256) {
|
|
4834
|
+
unavailable.push(relativePath);
|
|
4835
|
+
continue;
|
|
4836
|
+
}
|
|
4837
|
+
copyable.push({ asset, source });
|
|
4838
|
+
}
|
|
4839
|
+
if (fields.confirmWrite) {
|
|
4840
|
+
for (const item of copyable) {
|
|
4841
|
+
const target = swissifierFilePath(manifest, item.asset);
|
|
4842
|
+
mkdirSync5(dirname3(target), { recursive: true });
|
|
4843
|
+
copyFileSync2(item.source, target);
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
return {
|
|
4847
|
+
ok: true,
|
|
4848
|
+
demo_id: manifest.id,
|
|
4849
|
+
project,
|
|
4850
|
+
dryRun: !fields.confirmWrite,
|
|
4851
|
+
media_root: join8(repoRoot, ".asset-scratch"),
|
|
4852
|
+
restored: fields.confirmWrite ? copyable.length : 0,
|
|
4853
|
+
total: manifest.assets.length,
|
|
4854
|
+
would_restore: fields.confirmWrite ? 0 : copyable.length,
|
|
4855
|
+
source_required: false,
|
|
4856
|
+
source_env: manifest.media.source_env,
|
|
4857
|
+
source_hint: manifest.media.source_hint,
|
|
4858
|
+
missing: before.missing,
|
|
4859
|
+
invalid: before.invalid,
|
|
4860
|
+
unavailable
|
|
4595
4861
|
};
|
|
4596
4862
|
}
|
|
4597
4863
|
function svg(label, fill, stroke) {
|
|
@@ -4609,13 +4875,14 @@ function assetIdFor(asset) {
|
|
|
4609
4875
|
function demoAssetIds() {
|
|
4610
4876
|
return Object.fromEntries(demoAssets.map((asset) => [asset.key, assetIdFor(asset)]));
|
|
4611
4877
|
}
|
|
4878
|
+
function writeDemoFile(project, asset) {
|
|
4879
|
+
const path = demoFilePath(project, asset);
|
|
4880
|
+
mkdirSync5(dirname3(path), { recursive: true });
|
|
4881
|
+
writeFileSync3(path, svg(asset.label, asset.fill, asset.stroke));
|
|
4882
|
+
}
|
|
4612
4883
|
function writeDemoFiles(project) {
|
|
4613
4884
|
const ids = demoAssetIds();
|
|
4614
|
-
for (const asset of demoAssets)
|
|
4615
|
-
const path = join8(demoProjectDir(project), asset.channel, asset.file);
|
|
4616
|
-
mkdirSync5(dirname3(path), { recursive: true });
|
|
4617
|
-
writeFileSync3(path, svg(asset.label, asset.fill, asset.stroke));
|
|
4618
|
-
}
|
|
4885
|
+
for (const asset of demoAssets) writeDemoFile(project, asset);
|
|
4619
4886
|
return ids;
|
|
4620
4887
|
}
|
|
4621
4888
|
function upsertDemoAssets(project, ids) {
|
|
@@ -4664,6 +4931,55 @@ function upsertDemoAssets(project, ids) {
|
|
|
4664
4931
|
}
|
|
4665
4932
|
return { catalog: 0, local: demoAssets.length, total: demoAssets.length };
|
|
4666
4933
|
}
|
|
4934
|
+
function upsertSwissifierAssets(project, manifest) {
|
|
4935
|
+
const database = lineageDb();
|
|
4936
|
+
const timestamp = nowIso();
|
|
4937
|
+
try {
|
|
4938
|
+
database.prepare(`
|
|
4939
|
+
insert into projects (id, product, catalog_path, created_at, updated_at)
|
|
4940
|
+
values (?, ?, ?, ?, ?)
|
|
4941
|
+
on conflict(id) do update set product = excluded.product, updated_at = excluded.updated_at
|
|
4942
|
+
`).run(project, project, join8(repoRoot, project, "assets", "catalog.json"), timestamp, timestamp);
|
|
4943
|
+
const assetStatement = database.prepare(`
|
|
4944
|
+
insert into assets (
|
|
4945
|
+
id, project_id, source, local_path, s3_key, checksum_sha256, media_type, title, status,
|
|
4946
|
+
channel, campaign, audience, size_bytes, content_type, created_at, updated_at, last_seen_at
|
|
4947
|
+
) values (?, ?, 'local', ?, null, ?, 'image', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4948
|
+
on conflict(id) do update set
|
|
4949
|
+
project_id = excluded.project_id, source = excluded.source, local_path = excluded.local_path, checksum_sha256 = excluded.checksum_sha256,
|
|
4950
|
+
media_type = excluded.media_type, title = excluded.title, status = excluded.status, channel = excluded.channel,
|
|
4951
|
+
campaign = excluded.campaign, audience = excluded.audience, size_bytes = excluded.size_bytes,
|
|
4952
|
+
content_type = excluded.content_type, updated_at = excluded.updated_at, last_seen_at = excluded.last_seen_at
|
|
4953
|
+
`);
|
|
4954
|
+
const reviewStatement = database.prepare(`
|
|
4955
|
+
insert into asset_reviews (asset_id, review_state, updated_at)
|
|
4956
|
+
values (?, 'unreviewed', ?)
|
|
4957
|
+
on conflict(asset_id) do nothing
|
|
4958
|
+
`);
|
|
4959
|
+
for (const asset of manifest.assets) {
|
|
4960
|
+
assetStatement.run(
|
|
4961
|
+
asset.asset_id,
|
|
4962
|
+
project,
|
|
4963
|
+
swissifierRelativePath(manifest, asset),
|
|
4964
|
+
asset.checksum_sha256,
|
|
4965
|
+
asset.title,
|
|
4966
|
+
asset.status,
|
|
4967
|
+
asset.channel,
|
|
4968
|
+
manifest.campaign,
|
|
4969
|
+
manifest.audience,
|
|
4970
|
+
asset.size_bytes,
|
|
4971
|
+
asset.content_type,
|
|
4972
|
+
timestamp,
|
|
4973
|
+
timestamp,
|
|
4974
|
+
timestamp
|
|
4975
|
+
);
|
|
4976
|
+
reviewStatement.run(asset.asset_id, timestamp);
|
|
4977
|
+
}
|
|
4978
|
+
} finally {
|
|
4979
|
+
database.close();
|
|
4980
|
+
}
|
|
4981
|
+
return { catalog: 0, local: manifest.assets.length, total: manifest.assets.length };
|
|
4982
|
+
}
|
|
4667
4983
|
function seedDemoLineageWorkspace(project, fields) {
|
|
4668
4984
|
const ids = fields.confirmWrite ? writeDemoFiles(project) : demoAssetIds();
|
|
4669
4985
|
const rootAssetId = ids.root;
|
|
@@ -4709,6 +5025,53 @@ function seedDemoLineageWorkspace(project, fields) {
|
|
|
4709
5025
|
workspace
|
|
4710
5026
|
};
|
|
4711
5027
|
}
|
|
5028
|
+
function seedSwissifierRichDemoWorkspace(project, fields) {
|
|
5029
|
+
const manifest = swissifierManifest();
|
|
5030
|
+
if (!fields.confirmWrite) {
|
|
5031
|
+
return {
|
|
5032
|
+
ok: true,
|
|
5033
|
+
dryRun: true,
|
|
5034
|
+
demo_id: manifest.id,
|
|
5035
|
+
root_asset_id: manifest.root_asset_id,
|
|
5036
|
+
workspace_id: lineageWorkspaceId(project, manifest.root_asset_id),
|
|
5037
|
+
media_status: swissifierRichDemoMediaStatus(project)
|
|
5038
|
+
};
|
|
5039
|
+
}
|
|
5040
|
+
const summary = upsertSwissifierAssets(project, manifest);
|
|
5041
|
+
for (const edge of manifest.edges) {
|
|
5042
|
+
linkLineageAssets(project, { parentAssetId: edge.parent, childAssetId: edge.child, confirmWrite: true });
|
|
5043
|
+
}
|
|
5044
|
+
updateSelectedAsset(project, {
|
|
5045
|
+
assetIds: manifest.selected_asset_ids,
|
|
5046
|
+
confirmWrite: true,
|
|
5047
|
+
maxSelections: manifest.selected_asset_ids.length,
|
|
5048
|
+
notes: "Swissifier rich demo bases selected for the next variation.",
|
|
5049
|
+
rootAssetId: manifest.root_asset_id
|
|
5050
|
+
});
|
|
5051
|
+
updateLineageLayout(project, {
|
|
5052
|
+
confirmWrite: true,
|
|
5053
|
+
rootAssetId: manifest.root_asset_id,
|
|
5054
|
+
positions: manifest.assets.map((asset) => ({ assetId: asset.asset_id, ...asset.position }))
|
|
5055
|
+
});
|
|
5056
|
+
const workspace = createLineageWorkspace(project, {
|
|
5057
|
+
activate: fields.activate !== false,
|
|
5058
|
+
confirmWrite: true,
|
|
5059
|
+
createdBy: "system",
|
|
5060
|
+
notes: manifest.notes,
|
|
5061
|
+
rootAssetId: manifest.root_asset_id,
|
|
5062
|
+
title: manifest.title
|
|
5063
|
+
}).workspace;
|
|
5064
|
+
return {
|
|
5065
|
+
ok: true,
|
|
5066
|
+
message: `Seeded ${manifest.title}`,
|
|
5067
|
+
demo_id: manifest.id,
|
|
5068
|
+
media_status: swissifierRichDemoMediaStatus(project),
|
|
5069
|
+
root_asset_id: manifest.root_asset_id,
|
|
5070
|
+
selected_asset_ids: manifest.selected_asset_ids,
|
|
5071
|
+
summary,
|
|
5072
|
+
workspace
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
4712
5075
|
function archiveDemoLineageWorkspace(project, confirmWrite) {
|
|
4713
5076
|
const ids = demoAssetIds();
|
|
4714
5077
|
const rootAssetId = ids.root;
|
|
@@ -4756,11 +5119,37 @@ function registerLineageWorkspaceRoutes(app2, projectFrom2, asyncRoute2) {
|
|
|
4756
5119
|
app2.post("/api/lineage-workspaces/demo/archive", asyncRoute2((req, res) => {
|
|
4757
5120
|
res.json(archiveDemoLineageWorkspace(projectFrom2(req), req.body.confirmWrite === true));
|
|
4758
5121
|
}));
|
|
4759
|
-
app2.
|
|
4760
|
-
res.json(
|
|
5122
|
+
app2.post("/api/lineage-workspaces/demo/swissifier/seed", asyncRoute2((req, res) => {
|
|
5123
|
+
res.json(seedSwissifierRichDemoWorkspace(projectFrom2(req), {
|
|
5124
|
+
activate: req.body.activate !== false,
|
|
5125
|
+
confirmWrite: req.body.confirmWrite === true
|
|
5126
|
+
}));
|
|
5127
|
+
}));
|
|
5128
|
+
app2.get("/api/lineage-workspaces/demo/media", asyncRoute2((req, res) => {
|
|
5129
|
+
res.json({ ok: true, status: demoSeedMediaStatus(projectFrom2(req)) });
|
|
5130
|
+
}));
|
|
5131
|
+
app2.get("/api/lineage-workspaces/demo/swissifier/media", asyncRoute2((req, res) => {
|
|
5132
|
+
res.json({ ok: true, status: swissifierRichDemoMediaStatus(projectFrom2(req)) });
|
|
4761
5133
|
}));
|
|
4762
5134
|
app2.post("/api/lineage-workspaces/demo/media/restore", asyncRoute2((req, res) => {
|
|
4763
|
-
res.json({ ok: true, result: restoreDemoSeedMedia({ confirmWrite: req.body.confirmWrite === true }) });
|
|
5135
|
+
res.json({ ok: true, result: restoreDemoSeedMedia(projectFrom2(req), { confirmWrite: req.body.confirmWrite === true }) });
|
|
5136
|
+
}));
|
|
5137
|
+
app2.post("/api/lineage-workspaces/demo/swissifier/media/restore", asyncRoute2((req, res) => {
|
|
5138
|
+
res.json({
|
|
5139
|
+
ok: true,
|
|
5140
|
+
result: restoreSwissifierRichDemoMedia(projectFrom2(req), {
|
|
5141
|
+
confirmWrite: req.body.confirmWrite === true,
|
|
5142
|
+
sourceDir: typeof req.body.sourceDir === "string" ? req.body.sourceDir : void 0
|
|
5143
|
+
})
|
|
5144
|
+
});
|
|
5145
|
+
}));
|
|
5146
|
+
app2.post("/api/lineage-workspaces/demo/swissifier/media/download", asyncRoute2(async (req, res) => {
|
|
5147
|
+
res.json({
|
|
5148
|
+
ok: true,
|
|
5149
|
+
result: await downloadSwissifierRichDemoMedia(projectFrom2(req), {
|
|
5150
|
+
confirmWrite: req.body.confirmWrite === true
|
|
5151
|
+
})
|
|
5152
|
+
});
|
|
4764
5153
|
}));
|
|
4765
5154
|
app2.get("/api/lineage-workspaces/:workspaceId", asyncRoute2((req, res) => {
|
|
4766
5155
|
res.json({
|
|
@@ -4789,7 +5178,7 @@ function registerLineageWorkspaceRoutes(app2, projectFrom2, asyncRoute2) {
|
|
|
4789
5178
|
// src/server.ts
|
|
4790
5179
|
var app = express2();
|
|
4791
5180
|
var port = Number(process.env.PORT || 5173);
|
|
4792
|
-
var host = process.env.HOST || "
|
|
5181
|
+
var host = process.env.HOST || "lineage.localhost";
|
|
4793
5182
|
var isProduction = process.env.NODE_ENV === "production";
|
|
4794
5183
|
var maxUploadBytes = Number(process.env.LINEAGE_MAX_UPLOAD_MB || 200) * 1024 * 1024;
|
|
4795
5184
|
var upload = multer({ dest: ensureUploadDir(), limits: { fileSize: maxUploadBytes } });
|
|
@@ -5092,7 +5481,7 @@ app.post(
|
|
|
5092
5481
|
);
|
|
5093
5482
|
if (isProduction) {
|
|
5094
5483
|
const dist = join9(repoRoot, "dist", "web");
|
|
5095
|
-
if (
|
|
5484
|
+
if (existsSync6(dist)) {
|
|
5096
5485
|
app.use(express2.static(dist));
|
|
5097
5486
|
app.get("*", (_req, res) => res.sendFile(join9(dist, "index.html")));
|
|
5098
5487
|
}
|