fluncle 0.86.0 → 0.87.0
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/bin/fluncle.mjs +61 -308
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -538,7 +538,7 @@ function parseVersion(version) {
|
|
|
538
538
|
var currentVersion;
|
|
539
539
|
var init_version = __esm(() => {
|
|
540
540
|
init_output();
|
|
541
|
-
currentVersion = "0.
|
|
541
|
+
currentVersion = "0.87.0".trim() ? "0.87.0".trim() : "0.1.0";
|
|
542
542
|
});
|
|
543
543
|
|
|
544
544
|
// src/update-notifier.ts
|
|
@@ -2251,7 +2251,7 @@ function parseVersion2(version) {
|
|
|
2251
2251
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2252
2252
|
var init_version2 = __esm(() => {
|
|
2253
2253
|
init_output();
|
|
2254
|
-
currentVersion2 = "0.
|
|
2254
|
+
currentVersion2 = "0.87.0".trim() ? "0.87.0".trim() : "0.1.0";
|
|
2255
2255
|
});
|
|
2256
2256
|
|
|
2257
2257
|
// ../../packages/registry/src/index.ts
|
|
@@ -4014,238 +4014,6 @@ var init_recordings = __esm(() => {
|
|
|
4014
4014
|
init_mixtape_set_video();
|
|
4015
4015
|
});
|
|
4016
4016
|
|
|
4017
|
-
// src/commands/mixtapes.ts
|
|
4018
|
-
var exports_mixtapes2 = {};
|
|
4019
|
-
__export(exports_mixtapes2, {
|
|
4020
|
-
mixtapesCommand: () => mixtapesCommand2,
|
|
4021
|
-
mixtapeUpdateCommand: () => mixtapeUpdateCommand2,
|
|
4022
|
-
mixtapeResyncCommand: () => mixtapeResyncCommand2,
|
|
4023
|
-
mixtapePublishCommand: () => mixtapePublishCommand2,
|
|
4024
|
-
mixtapeMembersCommand: () => mixtapeMembersCommand2,
|
|
4025
|
-
mixtapeListCommand: () => mixtapeListCommand,
|
|
4026
|
-
mixtapeGetCommand: () => mixtapeGetCommand,
|
|
4027
|
-
mixtapeDistributeCommand: () => mixtapeDistributeCommand2,
|
|
4028
|
-
mixtapeDeleteCommand: () => mixtapeDeleteCommand2,
|
|
4029
|
-
mixtapeCreateCommand: () => mixtapeCreateCommand2
|
|
4030
|
-
});
|
|
4031
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4032
|
-
async function mixtapesCommand2() {
|
|
4033
|
-
const response = await publicApiGet("/api/mixtapes");
|
|
4034
|
-
return response.mixtapes;
|
|
4035
|
-
}
|
|
4036
|
-
async function mixtapeCreateCommand2(options) {
|
|
4037
|
-
return adminApiPost("/api/admin/mixtapes", buildBody2(options));
|
|
4038
|
-
}
|
|
4039
|
-
async function mixtapeUpdateCommand2(id, options) {
|
|
4040
|
-
return adminApiPatch(`/api/admin/mixtapes/${encodeURIComponent(id)}`, buildBody2(options));
|
|
4041
|
-
}
|
|
4042
|
-
async function mixtapeMembersCommand2(id, refs, options) {
|
|
4043
|
-
const members = refs.map((ref) => ({ ref }));
|
|
4044
|
-
if (options.from) {
|
|
4045
|
-
members.push(...parseCueFile2(options.from));
|
|
4046
|
-
}
|
|
4047
|
-
return adminApiPut(`/api/admin/mixtapes/${encodeURIComponent(id)}/members`, { members });
|
|
4048
|
-
}
|
|
4049
|
-
async function mixtapePublishCommand2(id) {
|
|
4050
|
-
return adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(id)}/publish`);
|
|
4051
|
-
}
|
|
4052
|
-
async function mixtapeDeleteCommand2(id) {
|
|
4053
|
-
return adminApiDelete(`/api/admin/mixtapes/${encodeURIComponent(id)}`);
|
|
4054
|
-
}
|
|
4055
|
-
async function mixtapeDistributeCommand2(idOrLogId, options, onProgress = () => {}) {
|
|
4056
|
-
const mixtape = await mixtapeGetCommand(idOrLogId);
|
|
4057
|
-
if (!mixtape.id) {
|
|
4058
|
-
throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
|
|
4059
|
-
}
|
|
4060
|
-
if (mixtape.status === "draft") {
|
|
4061
|
-
throw new CliError2("mixtape_not_promoted", `${mixtape.id} is still a draft — promote its recording first:
|
|
4062
|
-
` + " fluncle admin recordings promote <recordingId>");
|
|
4063
|
-
}
|
|
4064
|
-
const both = !options.youtube && !options.mixcloud;
|
|
4065
|
-
const doYoutube = both || Boolean(options.youtube);
|
|
4066
|
-
const doMixcloud = both || Boolean(options.mixcloud);
|
|
4067
|
-
if (doYoutube && !options.video) {
|
|
4068
|
-
throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
|
|
4069
|
-
}
|
|
4070
|
-
if (doMixcloud && !options.audio) {
|
|
4071
|
-
throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
|
|
4072
|
-
}
|
|
4073
|
-
const mixtapeId = mixtape.id;
|
|
4074
|
-
const logId = mixtape.logId;
|
|
4075
|
-
if (!logId) {
|
|
4076
|
-
throw new CliError2("missing_log_id", "The mixtape has no Log ID — was it promoted successfully?");
|
|
4077
|
-
}
|
|
4078
|
-
if (!mixtape.durationMs) {
|
|
4079
|
-
const source = options.audio ?? options.video;
|
|
4080
|
-
const durationMs = source ? await probeDurationMs2(source) : undefined;
|
|
4081
|
-
if (durationMs) {
|
|
4082
|
-
await mixtapeUpdateCommand2(mixtapeId, { durationMs: String(durationMs), json: false });
|
|
4083
|
-
onProgress(`Duration: ${Math.round(durationMs / 60000)} min (from the upload).`);
|
|
4084
|
-
}
|
|
4085
|
-
}
|
|
4086
|
-
if (mixtape.status === "published") {
|
|
4087
|
-
onProgress(`Already published (${logId}); re-distributing.`);
|
|
4088
|
-
} else {
|
|
4089
|
-
onProgress(`Distributing ${logId} (promoted — coordinate already minted).`);
|
|
4090
|
-
}
|
|
4091
|
-
const results = [];
|
|
4092
|
-
if (doYoutube) {
|
|
4093
|
-
if (!options.video) {
|
|
4094
|
-
throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
|
|
4095
|
-
}
|
|
4096
|
-
onProgress("YouTube: uploading video…");
|
|
4097
|
-
const { distributeYoutube: distributeYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
|
|
4098
|
-
const result = await distributeYoutube3(mixtapeId, options.video, onProgress);
|
|
4099
|
-
results.push({ platform: "youtube", url: result.url });
|
|
4100
|
-
onProgress(`YouTube: ${result.url}`);
|
|
4101
|
-
}
|
|
4102
|
-
if (doMixcloud) {
|
|
4103
|
-
if (!options.audio) {
|
|
4104
|
-
throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
|
|
4105
|
-
}
|
|
4106
|
-
onProgress("Mixcloud: uploading audio…");
|
|
4107
|
-
const { distributeMixcloud: distributeMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
|
|
4108
|
-
const result = await distributeMixcloud2(mixtapeId, options.audio, onProgress, options.unlisted);
|
|
4109
|
-
results.push({ platform: "mixcloud", url: result.url });
|
|
4110
|
-
onProgress(`Mixcloud: ${result.url}`);
|
|
4111
|
-
}
|
|
4112
|
-
return { logId, mixtapeId, results };
|
|
4113
|
-
}
|
|
4114
|
-
async function mixtapeResyncCommand2(idOrLogId, options, onProgress = () => {}) {
|
|
4115
|
-
const mixtape = await mixtapeGetCommand(idOrLogId);
|
|
4116
|
-
if (!mixtape.id) {
|
|
4117
|
-
throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
|
|
4118
|
-
}
|
|
4119
|
-
if (!mixtape.logId) {
|
|
4120
|
-
throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet — distribute it before re-syncing.");
|
|
4121
|
-
}
|
|
4122
|
-
const mixtapeId = mixtape.id;
|
|
4123
|
-
const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
|
|
4124
|
-
let doYoutube = Boolean(options.youtube);
|
|
4125
|
-
let doMixcloud = Boolean(options.mixcloud);
|
|
4126
|
-
if (!explicit) {
|
|
4127
|
-
const social = await adminApiGet(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/social`);
|
|
4128
|
-
const platforms = new Set(social.posts.map((post) => post.platform));
|
|
4129
|
-
doYoutube = platforms.has("youtube");
|
|
4130
|
-
doMixcloud = platforms.has("mixcloud");
|
|
4131
|
-
if (!doYoutube && !doMixcloud) {
|
|
4132
|
-
throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
const results = [];
|
|
4136
|
-
if (doYoutube) {
|
|
4137
|
-
onProgress("YouTube: re-syncing description + chapters…");
|
|
4138
|
-
const { resyncYoutube: resyncYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
|
|
4139
|
-
const result = await resyncYoutube3(mixtapeId);
|
|
4140
|
-
results.push({ platform: "youtube", url: result.url });
|
|
4141
|
-
onProgress(`YouTube: ${result.url}`);
|
|
4142
|
-
}
|
|
4143
|
-
if (doMixcloud) {
|
|
4144
|
-
onProgress("Mixcloud: re-syncing sections…");
|
|
4145
|
-
const { resyncMixcloud: resyncMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
|
|
4146
|
-
const result = await resyncMixcloud2(mixtapeId);
|
|
4147
|
-
results.push({ platform: "mixcloud", url: result.url });
|
|
4148
|
-
onProgress(`Mixcloud: ${result.url}`);
|
|
4149
|
-
}
|
|
4150
|
-
return { logId: mixtape.logId, mixtapeId, results };
|
|
4151
|
-
}
|
|
4152
|
-
async function probeDurationMs2(filePath) {
|
|
4153
|
-
try {
|
|
4154
|
-
const proc = Bun.spawn(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", filePath], { stderr: "ignore", stdout: "pipe" });
|
|
4155
|
-
const out = await new Response(proc.stdout).text();
|
|
4156
|
-
await proc.exited;
|
|
4157
|
-
const seconds = Number.parseFloat(out.trim());
|
|
4158
|
-
return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds * 1000) : undefined;
|
|
4159
|
-
} catch {
|
|
4160
|
-
return;
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4163
|
-
function buildBody2(options) {
|
|
4164
|
-
const body = {};
|
|
4165
|
-
if (options.note !== undefined) {
|
|
4166
|
-
body.note = options.note;
|
|
4167
|
-
}
|
|
4168
|
-
if (options.recordedAt !== undefined) {
|
|
4169
|
-
body.recordedAt = options.recordedAt;
|
|
4170
|
-
}
|
|
4171
|
-
if (options.soundcloudUrl !== undefined) {
|
|
4172
|
-
body.soundcloudUrl = options.soundcloudUrl;
|
|
4173
|
-
}
|
|
4174
|
-
if (options.durationMs !== undefined) {
|
|
4175
|
-
const parsed = parseDuration(options.durationMs);
|
|
4176
|
-
if (parsed === null) {
|
|
4177
|
-
throw new CliError2("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
|
|
4178
|
-
}
|
|
4179
|
-
body.durationMs = parsed;
|
|
4180
|
-
}
|
|
4181
|
-
return body;
|
|
4182
|
-
}
|
|
4183
|
-
function parseCueFile2(filePath) {
|
|
4184
|
-
if (!existsSync4(filePath)) {
|
|
4185
|
-
throw new CliError2("file_not_found", `Cue file not found: ${filePath}`);
|
|
4186
|
-
}
|
|
4187
|
-
const text = readFileSync4(filePath, "utf-8");
|
|
4188
|
-
const trimmed = text.trim();
|
|
4189
|
-
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
4190
|
-
try {
|
|
4191
|
-
const parsed = JSON.parse(trimmed);
|
|
4192
|
-
if (!Array.isArray(parsed)) {
|
|
4193
|
-
throw new Error("not an array");
|
|
4194
|
-
}
|
|
4195
|
-
return parsed.map((entry, index) => {
|
|
4196
|
-
if (typeof entry === "string") {
|
|
4197
|
-
return { ref: entry.trim() };
|
|
4198
|
-
}
|
|
4199
|
-
const obj = entry;
|
|
4200
|
-
if (typeof obj?.ref !== "string") {
|
|
4201
|
-
throw new CliError2("invalid_cue_json", `Entry ${index + 1} missing "ref" string`);
|
|
4202
|
-
}
|
|
4203
|
-
const cue = { ref: obj.ref.trim() };
|
|
4204
|
-
if (typeof obj.startMs === "number" && Number.isInteger(obj.startMs) && obj.startMs >= 0) {
|
|
4205
|
-
cue.startMs = obj.startMs;
|
|
4206
|
-
}
|
|
4207
|
-
return cue;
|
|
4208
|
-
});
|
|
4209
|
-
} catch (error) {
|
|
4210
|
-
if (error instanceof CliError2) {
|
|
4211
|
-
throw error;
|
|
4212
|
-
}
|
|
4213
|
-
throw new CliError2("invalid_cue_json", `Cue JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
return parseCueSheet2(text);
|
|
4217
|
-
}
|
|
4218
|
-
function parseCueSheet2(text) {
|
|
4219
|
-
const entries = [];
|
|
4220
|
-
for (const line of text.split(/\r?\n/)) {
|
|
4221
|
-
const trimmed = line.trim();
|
|
4222
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
4223
|
-
continue;
|
|
4224
|
-
}
|
|
4225
|
-
const match = trimmed.match(/^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)$/);
|
|
4226
|
-
if (match) {
|
|
4227
|
-
const [, time, ref] = match;
|
|
4228
|
-
if (time === undefined || ref === undefined) {
|
|
4229
|
-
continue;
|
|
4230
|
-
}
|
|
4231
|
-
const startMs = parseDuration(time);
|
|
4232
|
-
if (startMs === null) {
|
|
4233
|
-
continue;
|
|
4234
|
-
}
|
|
4235
|
-
entries.push({ ref: ref.trim(), startMs });
|
|
4236
|
-
} else {
|
|
4237
|
-
entries.push({ ref: trimmed });
|
|
4238
|
-
}
|
|
4239
|
-
}
|
|
4240
|
-
return entries;
|
|
4241
|
-
}
|
|
4242
|
-
var init_mixtapes2 = __esm(() => {
|
|
4243
|
-
init_util();
|
|
4244
|
-
init_api();
|
|
4245
|
-
init_mixtape_api();
|
|
4246
|
-
init_output();
|
|
4247
|
-
});
|
|
4248
|
-
|
|
4249
4017
|
// src/commands/clips.ts
|
|
4250
4018
|
var exports_clips = {};
|
|
4251
4019
|
__export(exports_clips, {
|
|
@@ -4321,9 +4089,6 @@ async function clipsListCommand(filter = {}) {
|
|
|
4321
4089
|
if (filter.recordingId) {
|
|
4322
4090
|
params.set("recordingId", filter.recordingId);
|
|
4323
4091
|
}
|
|
4324
|
-
if (filter.mixtapeId) {
|
|
4325
|
-
params.set("mixtapeId", filter.mixtapeId);
|
|
4326
|
-
}
|
|
4327
4092
|
if (filter.status) {
|
|
4328
4093
|
params.set("status", filter.status);
|
|
4329
4094
|
}
|
|
@@ -4332,28 +4097,17 @@ async function clipsListCommand(filter = {}) {
|
|
|
4332
4097
|
return response.clips;
|
|
4333
4098
|
}
|
|
4334
4099
|
async function resolveClipSource(clip) {
|
|
4335
|
-
if (clip.recordingId) {
|
|
4336
|
-
|
|
4337
|
-
const recording = await recordingGet2(clip.recordingId);
|
|
4338
|
-
if (!recording.r2Key) {
|
|
4339
|
-
throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
|
|
4340
|
-
}
|
|
4341
|
-
return {
|
|
4342
|
-
setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
|
|
4343
|
-
};
|
|
4344
|
-
}
|
|
4345
|
-
if (!clip.mixtapeId) {
|
|
4346
|
-
throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to neither a recording nor a mixtape`);
|
|
4100
|
+
if (!clip.recordingId) {
|
|
4101
|
+
throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
|
|
4347
4102
|
}
|
|
4348
|
-
const {
|
|
4349
|
-
const
|
|
4350
|
-
if (!
|
|
4351
|
-
throw new CliError2("
|
|
4103
|
+
const { recordingGet: recordingGet2 } = await Promise.resolve().then(() => (init_recordings(), exports_recordings));
|
|
4104
|
+
const recording = await recordingGet2(clip.recordingId);
|
|
4105
|
+
if (!recording.r2Key) {
|
|
4106
|
+
throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
|
|
4352
4107
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
}
|
|
4356
|
-
return { setUrl: setVideoUrl(mixtape.logId) };
|
|
4108
|
+
return {
|
|
4109
|
+
setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
|
|
4110
|
+
};
|
|
4357
4111
|
}
|
|
4358
4112
|
async function clipCutCommand(clipId, onProgress = () => {}) {
|
|
4359
4113
|
const clips = await clipsListCommand();
|
|
@@ -4447,7 +4201,7 @@ __export(exports_recordings2, {
|
|
|
4447
4201
|
recordingDeleteCommand: () => recordingDeleteCommand2,
|
|
4448
4202
|
recordingCreateCommand: () => recordingCreateCommand2
|
|
4449
4203
|
});
|
|
4450
|
-
import { existsSync as
|
|
4204
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4451
4205
|
async function recordingGet2(id) {
|
|
4452
4206
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
4453
4207
|
return response.recording;
|
|
@@ -4516,7 +4270,7 @@ async function recordingCreateCommand2(options = {}) {
|
|
|
4516
4270
|
if (!options.video) {
|
|
4517
4271
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
4518
4272
|
}
|
|
4519
|
-
if (!
|
|
4273
|
+
if (!existsSync4(options.video)) {
|
|
4520
4274
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
4521
4275
|
}
|
|
4522
4276
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -4552,11 +4306,11 @@ async function recordingUpdateCommand2(id, options = {}) {
|
|
|
4552
4306
|
body.parentId = options.parentId;
|
|
4553
4307
|
}
|
|
4554
4308
|
if (options.tracklistFile !== undefined) {
|
|
4555
|
-
if (!
|
|
4309
|
+
if (!existsSync4(options.tracklistFile)) {
|
|
4556
4310
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
4557
4311
|
}
|
|
4558
4312
|
try {
|
|
4559
|
-
body.tracklistJson = JSON.parse(
|
|
4313
|
+
body.tracklistJson = JSON.parse(readFileSync4(options.tracklistFile, "utf8"));
|
|
4560
4314
|
} catch {
|
|
4561
4315
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
4562
4316
|
}
|
|
@@ -4601,12 +4355,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
|
|
|
4601
4355
|
if (!options.cuesFile) {
|
|
4602
4356
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
4603
4357
|
}
|
|
4604
|
-
if (!
|
|
4358
|
+
if (!existsSync4(options.cuesFile)) {
|
|
4605
4359
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
4606
4360
|
}
|
|
4607
4361
|
let cues;
|
|
4608
4362
|
try {
|
|
4609
|
-
cues = JSON.parse(
|
|
4363
|
+
cues = JSON.parse(readFileSync4(options.cuesFile, "utf8"));
|
|
4610
4364
|
} catch {
|
|
4611
4365
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4612
4366
|
}
|
|
@@ -4632,8 +4386,8 @@ __export(exports_newsletter, {
|
|
|
4632
4386
|
newsletterDraftCommand: () => newsletterDraftCommand,
|
|
4633
4387
|
newsletterDeleteCommand: () => newsletterDeleteCommand
|
|
4634
4388
|
});
|
|
4635
|
-
import { existsSync as
|
|
4636
|
-
function
|
|
4389
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
4390
|
+
function buildBody2(options, { requireContent }) {
|
|
4637
4391
|
const body = {};
|
|
4638
4392
|
if (options.contentFile !== undefined) {
|
|
4639
4393
|
body.contentJson = readContentFile(options.contentFile);
|
|
@@ -4652,10 +4406,10 @@ function buildBody3(options, { requireContent }) {
|
|
|
4652
4406
|
return body;
|
|
4653
4407
|
}
|
|
4654
4408
|
function readContentFile(filePath) {
|
|
4655
|
-
if (!
|
|
4409
|
+
if (!existsSync5(filePath)) {
|
|
4656
4410
|
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
4657
4411
|
}
|
|
4658
|
-
const text =
|
|
4412
|
+
const text = readFileSync5(filePath, "utf-8");
|
|
4659
4413
|
try {
|
|
4660
4414
|
return JSON.parse(text);
|
|
4661
4415
|
} catch (error) {
|
|
@@ -4663,10 +4417,10 @@ function readContentFile(filePath) {
|
|
|
4663
4417
|
}
|
|
4664
4418
|
}
|
|
4665
4419
|
async function newsletterDraftCommand(options) {
|
|
4666
|
-
return adminApiPost("/api/admin/newsletter/editions",
|
|
4420
|
+
return adminApiPost("/api/admin/newsletter/editions", buildBody2(options, { requireContent: true }));
|
|
4667
4421
|
}
|
|
4668
4422
|
async function newsletterUpdateCommand(id, options) {
|
|
4669
|
-
return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`,
|
|
4423
|
+
return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody2(options, { requireContent: false }));
|
|
4670
4424
|
}
|
|
4671
4425
|
async function newsletterSendCommand(id) {
|
|
4672
4426
|
return adminApiPost(`/api/admin/newsletter/editions/${encodeURIComponent(id)}/send`);
|
|
@@ -5290,7 +5044,7 @@ var init_format2 = __esm(() => {
|
|
|
5290
5044
|
});
|
|
5291
5045
|
|
|
5292
5046
|
// src/cli.ts
|
|
5293
|
-
import { existsSync as
|
|
5047
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
5294
5048
|
import path2 from "path";
|
|
5295
5049
|
|
|
5296
5050
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -7456,8 +7210,8 @@ function addListenCommands(program2) {
|
|
|
7456
7210
|
await runRecent(options, recentCommand3);
|
|
7457
7211
|
});
|
|
7458
7212
|
program2.command("mixtapes").description("Fluncle's checkpoint sets").option("--json", "Print JSON", false).action(async (options) => {
|
|
7459
|
-
const { mixtapesCommand:
|
|
7460
|
-
await runMixtapes(options,
|
|
7213
|
+
const { mixtapesCommand: mixtapesCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7214
|
+
await runMixtapes(options, mixtapesCommand2);
|
|
7461
7215
|
});
|
|
7462
7216
|
program2.command("open").description("Pick a track, open it in Spotify").argument("[target]").option("--app", "Open in the native app", false).option("--browser", "Open in the browser", false).option("--limit <limit>", "Number of recent tracks to choose from", "20").allowExcessArguments().action(async (target, options) => {
|
|
7463
7217
|
const openCommands = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
@@ -7619,24 +7373,24 @@ function addAdminCommands(program2) {
|
|
|
7619
7373
|
adminMixtapes.outputHelp();
|
|
7620
7374
|
});
|
|
7621
7375
|
adminMixtapes.command("create").description("Log a new mixtape draft").option("--duration-ms <duration>", "Duration (mm:ss, h:mm:ss, or ms)").option("--json", "Print JSON", false).option("--note <text>", "Operator note").option("--recorded-at <date>", "Recorded date (ISO)").option("--soundcloud-url <url>", "SoundCloud URL (manual; YouTube + Mixcloud come from distribute)").allowExcessArguments().action(async (options) => {
|
|
7622
|
-
const { mixtapeCreateCommand:
|
|
7623
|
-
await runMixtapeCreate(options,
|
|
7376
|
+
const { mixtapeCreateCommand: mixtapeCreateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7377
|
+
await runMixtapeCreate(options, mixtapeCreateCommand2);
|
|
7624
7378
|
});
|
|
7625
7379
|
adminMixtapes.command("update").description("Update a mixtape's fields").argument("[id]").option("--duration-ms <duration>", "Duration (mm:ss, h:mm:ss, or ms)").option("--json", "Print JSON", false).option("--note <text>", "Operator note").option("--recorded-at <date>", "Recorded date (ISO)").option("--soundcloud-url <url>", "SoundCloud URL (manual; YouTube + Mixcloud come from distribute)").allowExcessArguments().action(async (id, options) => {
|
|
7626
|
-
const { mixtapeUpdateCommand:
|
|
7627
|
-
await runMixtapeUpdate(id, options,
|
|
7380
|
+
const { mixtapeUpdateCommand: mixtapeUpdateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7381
|
+
await runMixtapeUpdate(id, options, mixtapeUpdateCommand2);
|
|
7628
7382
|
});
|
|
7629
7383
|
adminMixtapes.command("members").description("Set a mixtape's tracklist (refs and/or a cue-sheet file)").argument("[id]").argument("[refs...]").option("--from <file>", "Cue-sheet or JSON file with members").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, refs, options) => {
|
|
7630
|
-
const { mixtapeMembersCommand:
|
|
7631
|
-
await runMixtapeMembers(id, refs, options,
|
|
7384
|
+
const { mixtapeMembersCommand: mixtapeMembersCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7385
|
+
await runMixtapeMembers(id, refs, options, mixtapeMembersCommand2);
|
|
7632
7386
|
});
|
|
7633
7387
|
adminMixtapes.command("publish").description("Publish a mixtape draft").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
|
|
7634
|
-
const { mixtapePublishCommand:
|
|
7635
|
-
await runMixtapePublish(id, options,
|
|
7388
|
+
const { mixtapePublishCommand: mixtapePublishCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7389
|
+
await runMixtapePublish(id, options, mixtapePublishCommand2);
|
|
7636
7390
|
});
|
|
7637
7391
|
adminMixtapes.command("delete").description("Discard a mixtape draft (published mixtapes can't be deleted)").argument("[id]").option("--json", "Print JSON", false).option("--yes", "Skip confirmation", false).allowExcessArguments().action(async (id, options) => {
|
|
7638
|
-
const { mixtapeDeleteCommand:
|
|
7639
|
-
await runMixtapeDelete(id, options,
|
|
7392
|
+
const { mixtapeDeleteCommand: mixtapeDeleteCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7393
|
+
await runMixtapeDelete(id, options, mixtapeDeleteCommand2);
|
|
7640
7394
|
});
|
|
7641
7395
|
adminMixtapes.command("list").description("List all mixtapes (including drafts)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7642
7396
|
const { mixtapeListCommand: mixtapeListCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
@@ -7647,22 +7401,22 @@ function addAdminCommands(program2) {
|
|
|
7647
7401
|
await runMixtapeGet(idOrLogId, options, mixtapeGetCommand2);
|
|
7648
7402
|
});
|
|
7649
7403
|
adminMixtapes.command("distribute").description("Push a promoted mixtape to YouTube (video) and Mixcloud (audio). The mixtape must already be promoted (`recordings promote`) \u2014 distribute is push-only.").argument("[idOrLogId]").option("--video <file>", "Video file for YouTube").option("--audio <file>", "Audio file for Mixcloud").option("--youtube", "Only distribute to YouTube").option("--mixcloud", "Only distribute to Mixcloud").option("--unlisted", "Keep Mixcloud private too (YouTube is always unlisted until publish-youtube)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7650
|
-
const { mixtapeDistributeCommand:
|
|
7651
|
-
await runMixtapeDistribute(idOrLogId, options,
|
|
7404
|
+
const { mixtapeDistributeCommand: mixtapeDistributeCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7405
|
+
await runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2);
|
|
7652
7406
|
});
|
|
7653
7407
|
adminMixtapes.command("publish-youtube").description("Flip a distributed mixtape's YouTube video from unlisted to public").argument("[idOrLogId]").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7654
7408
|
const { publishYoutubeCommand: publishYoutubeCommand3 } = await Promise.resolve().then(() => (init_mixtape_youtube2(), exports_mixtape_youtube2));
|
|
7655
7409
|
await runMixtapePublishYoutube(idOrLogId, options, publishYoutubeCommand3);
|
|
7656
7410
|
});
|
|
7657
7411
|
adminMixtapes.command("resync").description("Re-push a published mixtape's YouTube chapters + Mixcloud sections from its current cues (no re-upload)").argument("[idOrLogId]").option("--youtube", "Only re-sync YouTube").option("--mixcloud", "Only re-sync Mixcloud").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7658
|
-
const { mixtapeResyncCommand:
|
|
7659
|
-
await runMixtapeResync(idOrLogId, options,
|
|
7412
|
+
const { mixtapeResyncCommand: mixtapeResyncCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7413
|
+
await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2);
|
|
7660
7414
|
});
|
|
7661
7415
|
const adminClips = configureCommand(admin.command("clips").description("Mixtape clip (Fluncle Studio) commands"));
|
|
7662
7416
|
adminClips.action(() => {
|
|
7663
7417
|
adminClips.outputHelp();
|
|
7664
7418
|
});
|
|
7665
|
-
adminClips.command("list").description("List clips (filter by --status pending|done
|
|
7419
|
+
adminClips.command("list").description("List clips (filter by --status pending|done and/or --recording <id>)").option("--status <status>", "Filter by cut status (pending|done)").option("--recording <id>", "Filter by recording id").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7666
7420
|
const { clipsListCommand: clipsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7667
7421
|
await runClipsList(options, clipsListCommand2);
|
|
7668
7422
|
});
|
|
@@ -7805,7 +7559,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
|
|
|
7805
7559
|
console.log(` mime: ${result.mime}`);
|
|
7806
7560
|
}
|
|
7807
7561
|
async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
|
|
7808
|
-
const script = options.scriptFile ?
|
|
7562
|
+
const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
|
|
7809
7563
|
if (!idOrLogId || !script || !script.trim()) {
|
|
7810
7564
|
throw new Error("Usage: fluncle admin tracks observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--duration-ms <ms>] [--context-note <text>] [--json]");
|
|
7811
7565
|
}
|
|
@@ -7861,7 +7615,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
|
|
|
7861
7615
|
}
|
|
7862
7616
|
}
|
|
7863
7617
|
async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
|
|
7864
|
-
const note = options.scriptFile ?
|
|
7618
|
+
const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
|
|
7865
7619
|
if (!idOrLogId || !note || !note.trim()) {
|
|
7866
7620
|
throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
|
|
7867
7621
|
}
|
|
@@ -8007,7 +7761,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
8007
7761
|
return;
|
|
8008
7762
|
}
|
|
8009
7763
|
const candidate = path2.join(dir, name);
|
|
8010
|
-
return
|
|
7764
|
+
return existsSync6(candidate) ? candidate : undefined;
|
|
8011
7765
|
};
|
|
8012
7766
|
const resolveFile = (explicit, name) => {
|
|
8013
7767
|
if (explicit) {
|
|
@@ -8184,56 +7938,56 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
|
|
|
8184
7938
|
}
|
|
8185
7939
|
console.log(result.noVideo ? `${result.logId} has no video \u2014 nothing to purge.` : `Purging the stale renditions for ${result.logId} from the edge. The next play picks up the fresh render.`);
|
|
8186
7940
|
}
|
|
8187
|
-
async function runMixtapeCreate(options,
|
|
8188
|
-
const result = await
|
|
7941
|
+
async function runMixtapeCreate(options, mixtapeCreateCommand2) {
|
|
7942
|
+
const result = await mixtapeCreateCommand2(options);
|
|
8189
7943
|
if (options.json) {
|
|
8190
7944
|
printJson(result);
|
|
8191
7945
|
return;
|
|
8192
7946
|
}
|
|
8193
7947
|
console.log(`Logged draft ${result.mixtape.id}. It stays a draft until you publish it.`);
|
|
8194
7948
|
}
|
|
8195
|
-
async function runMixtapeUpdate(id, options,
|
|
7949
|
+
async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
|
|
8196
7950
|
if (!id) {
|
|
8197
7951
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes update <id>");
|
|
8198
7952
|
}
|
|
8199
|
-
const result = await
|
|
7953
|
+
const result = await mixtapeUpdateCommand2(id, options);
|
|
8200
7954
|
if (options.json) {
|
|
8201
7955
|
printJson(result);
|
|
8202
7956
|
return;
|
|
8203
7957
|
}
|
|
8204
7958
|
console.log(`Saved ${result.mixtape.id}.`);
|
|
8205
7959
|
}
|
|
8206
|
-
async function runMixtapeMembers(id, refs, options,
|
|
7960
|
+
async function runMixtapeMembers(id, refs, options, mixtapeMembersCommand2) {
|
|
8207
7961
|
if (!id) {
|
|
8208
7962
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes members <id> [refs...]");
|
|
8209
7963
|
}
|
|
8210
7964
|
if (refs.length === 0 && !options.from) {
|
|
8211
7965
|
throw new Error("Provide refs as arguments or a cue-sheet file with --from");
|
|
8212
7966
|
}
|
|
8213
|
-
const result = await
|
|
7967
|
+
const result = await mixtapeMembersCommand2(id, refs, options);
|
|
8214
7968
|
if (options.json) {
|
|
8215
7969
|
printJson(result);
|
|
8216
7970
|
return;
|
|
8217
7971
|
}
|
|
8218
7972
|
console.log(`Saved the tracklist: ${result.mixtape.memberCount} bangers on ${result.mixtape.id}.`);
|
|
8219
7973
|
}
|
|
8220
|
-
async function runMixtapePublish(id, options,
|
|
7974
|
+
async function runMixtapePublish(id, options, mixtapePublishCommand2) {
|
|
8221
7975
|
if (!id) {
|
|
8222
7976
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes publish <id>");
|
|
8223
7977
|
}
|
|
8224
|
-
const result = await
|
|
7978
|
+
const result = await mixtapePublishCommand2(id);
|
|
8225
7979
|
if (options.json) {
|
|
8226
7980
|
printJson(result);
|
|
8227
7981
|
return;
|
|
8228
7982
|
}
|
|
8229
7983
|
console.log(`Minted ${result.mixtape.logId} (fluncle://${result.mixtape.logId}) \u2014 distributing. ` + "Run `distribute` to push it to the platforms.");
|
|
8230
7984
|
}
|
|
8231
|
-
async function runMixtapeDistribute(idOrLogId, options,
|
|
7985
|
+
async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2) {
|
|
8232
7986
|
if (!idOrLogId) {
|
|
8233
7987
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes distribute <idOrLogId> --video <mp4> --audio <file>");
|
|
8234
7988
|
}
|
|
8235
7989
|
const onProgress = options.json ? () => {} : (message) => console.log(message);
|
|
8236
|
-
const result = await
|
|
7990
|
+
const result = await mixtapeDistributeCommand2(idOrLogId, options, onProgress);
|
|
8237
7991
|
if (options.json) {
|
|
8238
7992
|
printJson(result);
|
|
8239
7993
|
return;
|
|
@@ -8254,12 +8008,12 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
|
|
|
8254
8008
|
}
|
|
8255
8009
|
console.log(`YouTube video is now public: ${result.url}`);
|
|
8256
8010
|
}
|
|
8257
|
-
async function runMixtapeResync(idOrLogId, options,
|
|
8011
|
+
async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2) {
|
|
8258
8012
|
if (!idOrLogId) {
|
|
8259
8013
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes resync <idOrLogId>");
|
|
8260
8014
|
}
|
|
8261
8015
|
const onProgress = options.json ? () => {} : (message) => console.log(message);
|
|
8262
|
-
const result = await
|
|
8016
|
+
const result = await mixtapeResyncCommand2(idOrLogId, options, onProgress);
|
|
8263
8017
|
if (options.json) {
|
|
8264
8018
|
printJson(result);
|
|
8265
8019
|
return;
|
|
@@ -8269,14 +8023,14 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
|
|
|
8269
8023
|
console.log(`Re-synced ${result.logId} (fluncle://${result.logId}).
|
|
8270
8024
|
${links}`);
|
|
8271
8025
|
}
|
|
8272
|
-
async function runMixtapeDelete(id, options,
|
|
8026
|
+
async function runMixtapeDelete(id, options, mixtapeDeleteCommand2) {
|
|
8273
8027
|
if (!id) {
|
|
8274
8028
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes delete <id> --yes");
|
|
8275
8029
|
}
|
|
8276
8030
|
if (!options.yes) {
|
|
8277
8031
|
throw new Error("Pass --yes to confirm the discard. Published mixtapes can't be deleted.");
|
|
8278
8032
|
}
|
|
8279
|
-
await
|
|
8033
|
+
await mixtapeDeleteCommand2(id);
|
|
8280
8034
|
if (options.json) {
|
|
8281
8035
|
printJson({ id, ok: true });
|
|
8282
8036
|
return;
|
|
@@ -8301,7 +8055,6 @@ async function runMixtapeList(options, mixtapeListCommand2) {
|
|
|
8301
8055
|
}
|
|
8302
8056
|
async function runClipsList(options, clipsListCommand2) {
|
|
8303
8057
|
const clips = await clipsListCommand2({
|
|
8304
|
-
mixtapeId: options.mixtape,
|
|
8305
8058
|
recordingId: options.recording,
|
|
8306
8059
|
status: options.status
|
|
8307
8060
|
});
|
|
@@ -8314,7 +8067,7 @@ async function runClipsList(options, clipsListCommand2) {
|
|
|
8314
8067
|
return;
|
|
8315
8068
|
}
|
|
8316
8069
|
for (const clip of clips) {
|
|
8317
|
-
const source = clip.recordingId ??
|
|
8070
|
+
const source = clip.recordingId ?? "\u2014";
|
|
8318
8071
|
console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset}`);
|
|
8319
8072
|
}
|
|
8320
8073
|
}
|
|
@@ -8477,8 +8230,8 @@ async function runRecent(options, recentCommand3) {
|
|
|
8477
8230
|
console.log(trackRows2(tracks).join(`
|
|
8478
8231
|
`));
|
|
8479
8232
|
}
|
|
8480
|
-
async function runMixtapes(options,
|
|
8481
|
-
const mixtapes = await
|
|
8233
|
+
async function runMixtapes(options, mixtapesCommand2) {
|
|
8234
|
+
const mixtapes = await mixtapesCommand2();
|
|
8482
8235
|
if (options.json) {
|
|
8483
8236
|
printJson({ mixtapes, ok: true });
|
|
8484
8237
|
return;
|
package/package.json
CHANGED