@wayai/cli 0.3.203 → 0.3.204
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/dist/index.js +2446 -535
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -447,11 +447,11 @@ function captureException2(error, context) {
|
|
|
447
447
|
Sentry.captureException(error);
|
|
448
448
|
});
|
|
449
449
|
}
|
|
450
|
-
function addApiBreadcrumb(method,
|
|
450
|
+
function addApiBreadcrumb(method, path41) {
|
|
451
451
|
if (!initialized) return;
|
|
452
452
|
Sentry.addBreadcrumb({
|
|
453
453
|
category: "http",
|
|
454
|
-
message: `${method} ${
|
|
454
|
+
message: `${method} ${path41}`,
|
|
455
455
|
level: "info"
|
|
456
456
|
});
|
|
457
457
|
}
|
|
@@ -2944,8 +2944,8 @@ var init_parseUtil = __esm({
|
|
|
2944
2944
|
init_errors();
|
|
2945
2945
|
init_en();
|
|
2946
2946
|
makeIssue = (params) => {
|
|
2947
|
-
const { data, path:
|
|
2948
|
-
const fullPath = [...
|
|
2947
|
+
const { data, path: path41, errorMaps, issueData } = params;
|
|
2948
|
+
const fullPath = [...path41, ...issueData.path || []];
|
|
2949
2949
|
const fullIssue = {
|
|
2950
2950
|
...issueData,
|
|
2951
2951
|
path: fullPath
|
|
@@ -3256,11 +3256,11 @@ var init_types = __esm({
|
|
|
3256
3256
|
init_parseUtil();
|
|
3257
3257
|
init_util();
|
|
3258
3258
|
ParseInputLazyPath = class {
|
|
3259
|
-
constructor(parent, value,
|
|
3259
|
+
constructor(parent, value, path41, key) {
|
|
3260
3260
|
this._cachedPath = [];
|
|
3261
3261
|
this.parent = parent;
|
|
3262
3262
|
this.data = value;
|
|
3263
|
-
this._path =
|
|
3263
|
+
this._path = path41;
|
|
3264
3264
|
this._key = key;
|
|
3265
3265
|
}
|
|
3266
3266
|
get path() {
|
|
@@ -6952,12 +6952,12 @@ function typeMatches(typeField, allowed) {
|
|
|
6952
6952
|
if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
|
|
6953
6953
|
return false;
|
|
6954
6954
|
}
|
|
6955
|
-
function validateSchema(schema,
|
|
6955
|
+
function validateSchema(schema, path41, errors, opts = {}) {
|
|
6956
6956
|
if (typeof schema === "boolean") return;
|
|
6957
6957
|
const depth = opts.depth ?? 0;
|
|
6958
6958
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
6959
6959
|
errors.push({
|
|
6960
|
-
path:
|
|
6960
|
+
path: path41 || "<root>",
|
|
6961
6961
|
message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
|
|
6962
6962
|
suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
|
|
6963
6963
|
});
|
|
@@ -6965,7 +6965,7 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
6965
6965
|
}
|
|
6966
6966
|
if (!isRecord2(schema)) {
|
|
6967
6967
|
errors.push({
|
|
6968
|
-
path:
|
|
6968
|
+
path: path41,
|
|
6969
6969
|
message: `expected object, got ${schema === null ? "null" : typeof schema}`
|
|
6970
6970
|
});
|
|
6971
6971
|
return;
|
|
@@ -6973,14 +6973,14 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
6973
6973
|
if (opts.isRoot) {
|
|
6974
6974
|
if ("type" in schema && schema.type !== "object") {
|
|
6975
6975
|
errors.push({
|
|
6976
|
-
path:
|
|
6976
|
+
path: path41 ? `${path41}.type` : "type",
|
|
6977
6977
|
message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
|
|
6978
6978
|
suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
|
|
6979
6979
|
});
|
|
6980
6980
|
}
|
|
6981
6981
|
} else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
|
|
6982
6982
|
errors.push({
|
|
6983
|
-
path: `${
|
|
6983
|
+
path: `${path41}.type`,
|
|
6984
6984
|
message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
|
|
6985
6985
|
});
|
|
6986
6986
|
}
|
|
@@ -6990,25 +6990,25 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
6990
6990
|
const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
|
|
6991
6991
|
const isOutcomePlaceholder = e === "[KANBAN_OUTCOME_VALUES]";
|
|
6992
6992
|
errors.push({
|
|
6993
|
-
path: `${
|
|
6993
|
+
path: `${path41}.enum`,
|
|
6994
6994
|
message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
|
|
6995
6995
|
suggestion: isOutcomePlaceholder ? "Declare `outcomes` on the hub's terminal kanban status so the platform can render the placeholder into a valid array; with none configured the platform drops the parameter instead." : isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
|
|
6996
6996
|
});
|
|
6997
6997
|
} else if (!Array.isArray(e)) {
|
|
6998
6998
|
errors.push({
|
|
6999
|
-
path: `${
|
|
6999
|
+
path: `${path41}.enum`,
|
|
7000
7000
|
message: `enum must be a non-empty array of primitives, got ${typeof e}`
|
|
7001
7001
|
});
|
|
7002
7002
|
} else if (e.length === 0) {
|
|
7003
7003
|
errors.push({
|
|
7004
|
-
path: `${
|
|
7004
|
+
path: `${path41}.enum`,
|
|
7005
7005
|
message: "enum must not be empty"
|
|
7006
7006
|
});
|
|
7007
7007
|
} else {
|
|
7008
7008
|
for (let i = 0; i < e.length; i++) {
|
|
7009
7009
|
if (!isPrimitive(e[i])) {
|
|
7010
7010
|
errors.push({
|
|
7011
|
-
path: `${
|
|
7011
|
+
path: `${path41}.enum[${i}]`,
|
|
7012
7012
|
message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
|
|
7013
7013
|
});
|
|
7014
7014
|
}
|
|
@@ -7018,7 +7018,7 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
7018
7018
|
for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
|
|
7019
7019
|
if (key in schema && typeof schema[key] === "boolean") {
|
|
7020
7020
|
errors.push({
|
|
7021
|
-
path: `${
|
|
7021
|
+
path: `${path41}.${key}`,
|
|
7022
7022
|
message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
|
|
7023
7023
|
suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
|
|
7024
7024
|
});
|
|
@@ -7027,45 +7027,45 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
7027
7027
|
if ("properties" in schema) {
|
|
7028
7028
|
if (!isRecord2(schema.properties)) {
|
|
7029
7029
|
errors.push({
|
|
7030
|
-
path: `${
|
|
7030
|
+
path: `${path41}.properties`,
|
|
7031
7031
|
message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
|
|
7032
7032
|
});
|
|
7033
7033
|
} else {
|
|
7034
7034
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
7035
|
-
validateSchema(propSchema, `${
|
|
7035
|
+
validateSchema(propSchema, `${path41}.properties.${propName}`, errors, { depth: depth + 1 });
|
|
7036
7036
|
}
|
|
7037
7037
|
}
|
|
7038
7038
|
}
|
|
7039
7039
|
if (schemaTypeIncludes(schema, "array") && "items" in schema) {
|
|
7040
7040
|
if (Array.isArray(schema.items)) {
|
|
7041
|
-
schema.items.forEach((sub, i) => validateSchema(sub, `${
|
|
7041
|
+
schema.items.forEach((sub, i) => validateSchema(sub, `${path41}.items[${i}]`, errors, { depth: depth + 1 }));
|
|
7042
7042
|
} else {
|
|
7043
|
-
validateSchema(schema.items, `${
|
|
7043
|
+
validateSchema(schema.items, `${path41}.items`, errors, { depth: depth + 1 });
|
|
7044
7044
|
}
|
|
7045
7045
|
}
|
|
7046
7046
|
for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
|
|
7047
7047
|
if (key in schema && isRecord2(schema[key])) {
|
|
7048
|
-
validateSchema(schema[key], `${
|
|
7048
|
+
validateSchema(schema[key], `${path41}.${key}`, errors, { depth: depth + 1 });
|
|
7049
7049
|
}
|
|
7050
7050
|
}
|
|
7051
7051
|
for (const key of SUBSCHEMA_LIST_KEYWORDS) {
|
|
7052
7052
|
const list = schema[key];
|
|
7053
7053
|
if (Array.isArray(list)) {
|
|
7054
|
-
list.forEach((sub, i) => validateSchema(sub, `${
|
|
7054
|
+
list.forEach((sub, i) => validateSchema(sub, `${path41}.${key}[${i}]`, errors, { depth: depth + 1 }));
|
|
7055
7055
|
}
|
|
7056
7056
|
}
|
|
7057
7057
|
for (const key of SUBSCHEMA_MAP_KEYWORDS) {
|
|
7058
7058
|
const map = schema[key];
|
|
7059
7059
|
if (isRecord2(map)) {
|
|
7060
7060
|
for (const [name, sub] of Object.entries(map)) {
|
|
7061
|
-
validateSchema(sub, `${
|
|
7061
|
+
validateSchema(sub, `${path41}.${key}.${name}`, errors, { depth: depth + 1 });
|
|
7062
7062
|
}
|
|
7063
7063
|
}
|
|
7064
7064
|
}
|
|
7065
7065
|
const reportedPaths = new Set(errors.map((e) => e.path));
|
|
7066
7066
|
for (const [k, v] of Object.entries(schema)) {
|
|
7067
7067
|
if (typeof v !== "string") continue;
|
|
7068
|
-
const fieldPath = `${
|
|
7068
|
+
const fieldPath = `${path41}.${k}`;
|
|
7069
7069
|
if (reportedPaths.has(fieldPath)) continue;
|
|
7070
7070
|
for (const token of PLACEHOLDER_TOKENS) {
|
|
7071
7071
|
if (v === token) {
|
|
@@ -7121,12 +7121,12 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7121
7121
|
const linkTargets = /* @__PURE__ */ new Set();
|
|
7122
7122
|
followups.forEach((followup, i) => {
|
|
7123
7123
|
const ref = followup?.after_followup_id;
|
|
7124
|
-
const
|
|
7124
|
+
const path41 = ["followups", i, "after_followup_id"];
|
|
7125
7125
|
if (followup?.type !== "inactivity_after_before_event") {
|
|
7126
7126
|
if (ref !== void 0) {
|
|
7127
7127
|
ctx.addIssue({
|
|
7128
7128
|
code: external_exports.ZodIssueCode.custom,
|
|
7129
|
-
path:
|
|
7129
|
+
path: path41,
|
|
7130
7130
|
message: `kanban status ${label}: after_followup_id is only valid on inactivity_after_before_event followups (this one is ${followup?.type})`
|
|
7131
7131
|
});
|
|
7132
7132
|
}
|
|
@@ -7135,7 +7135,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7135
7135
|
if (ref === void 0) {
|
|
7136
7136
|
ctx.addIssue({
|
|
7137
7137
|
code: external_exports.ZodIssueCode.custom,
|
|
7138
|
-
path:
|
|
7138
|
+
path: path41,
|
|
7139
7139
|
message: `kanban status ${label}: inactivity_after_before_event requires after_followup_id naming the id of a before_event followup in the same status`
|
|
7140
7140
|
});
|
|
7141
7141
|
return;
|
|
@@ -7145,7 +7145,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7145
7145
|
if (matches.length > 1) {
|
|
7146
7146
|
ctx.addIssue({
|
|
7147
7147
|
code: external_exports.ZodIssueCode.custom,
|
|
7148
|
-
path:
|
|
7148
|
+
path: path41,
|
|
7149
7149
|
message: `kanban status ${label}: after_followup_id "${ref}" matches ${matches.length} followups \u2014 a link must name exactly one`
|
|
7150
7150
|
});
|
|
7151
7151
|
return;
|
|
@@ -7157,7 +7157,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7157
7157
|
);
|
|
7158
7158
|
ctx.addIssue({
|
|
7159
7159
|
code: external_exports.ZodIssueCode.custom,
|
|
7160
|
-
path:
|
|
7160
|
+
path: path41,
|
|
7161
7161
|
message: hasIdlessBeforeEvent ? `kanban status ${label}: after_followup_id "${ref}" matches no followup \u2014 a link target must declare an explicit id, and this status has before_event followups without one` : `kanban status ${label}: after_followup_id "${ref}" matches no followup in this status`
|
|
7162
7162
|
});
|
|
7163
7163
|
return;
|
|
@@ -7165,7 +7165,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7165
7165
|
if (target.type !== "before_event") {
|
|
7166
7166
|
ctx.addIssue({
|
|
7167
7167
|
code: external_exports.ZodIssueCode.custom,
|
|
7168
|
-
path:
|
|
7168
|
+
path: path41,
|
|
7169
7169
|
message: `kanban status ${label}: after_followup_id "${ref}" points at a ${target.type} followup \u2014 only before_event followups can anchor a chain`
|
|
7170
7170
|
});
|
|
7171
7171
|
}
|
|
@@ -7421,9 +7421,9 @@ function monitorRuleActions(monitorConfig) {
|
|
|
7421
7421
|
if (!monitorConfig || typeof monitorConfig !== "object") return [];
|
|
7422
7422
|
const config = monitorConfig;
|
|
7423
7423
|
const found = [];
|
|
7424
|
-
const visit = (action,
|
|
7424
|
+
const visit = (action, path41) => {
|
|
7425
7425
|
if (!action || typeof action !== "object" || Array.isArray(action)) return;
|
|
7426
|
-
found.push({ action, path:
|
|
7426
|
+
found.push({ action, path: path41 });
|
|
7427
7427
|
};
|
|
7428
7428
|
if (Array.isArray(config.rules)) {
|
|
7429
7429
|
config.rules.forEach((rule, index) => {
|
|
@@ -7455,25 +7455,25 @@ function collectMonitorRuleIssues(monitorConfig) {
|
|
|
7455
7455
|
const trigger = resolveMonitorTrigger(monitorConfig);
|
|
7456
7456
|
const kinds = monitorRuleActionKinds(trigger);
|
|
7457
7457
|
const issues = [...callUtteranceRuleIssues(monitorConfig), ...automationGateIssues(monitorConfig)];
|
|
7458
|
-
for (const { action, path:
|
|
7459
|
-
if (trigger === "call_utterance" &&
|
|
7458
|
+
for (const { action, path: path41 } of monitorRuleActions(monitorConfig)) {
|
|
7459
|
+
if (trigger === "call_utterance" && path41[0] === "fallback") continue;
|
|
7460
7460
|
const kind = action.kind;
|
|
7461
7461
|
if (typeof kind === "string" && !kinds.includes(kind)) {
|
|
7462
|
-
issues.push({ path: [...
|
|
7462
|
+
issues.push({ path: [...path41, "kind"], message: monitorActionKindMessage(kind, trigger) });
|
|
7463
7463
|
}
|
|
7464
7464
|
const toolName = action.tool_name;
|
|
7465
7465
|
if (kind === "call_tool" && typeof toolName === "string" && isRefusedNativeToolName(toolName, trigger)) {
|
|
7466
|
-
issues.push({ path: [...
|
|
7466
|
+
issues.push({ path: [...path41, "tool_name"], message: monitorRuleToolNotAllowedMessage(toolName, trigger) });
|
|
7467
7467
|
continue;
|
|
7468
7468
|
}
|
|
7469
7469
|
if (kind === "call_tool" && toolName === INSERT_NOTE_TOOL_NAME) {
|
|
7470
|
-
issues.push(...insertNoteArgumentIssues(action,
|
|
7470
|
+
issues.push(...insertNoteArgumentIssues(action, path41));
|
|
7471
7471
|
}
|
|
7472
7472
|
if (kind === "call_tool" && toolName === RUN_MONITOR_TOOL_NAME) {
|
|
7473
7473
|
const callee = readRunMonitorCallee(action);
|
|
7474
7474
|
if (!callee.ok) {
|
|
7475
7475
|
issues.push({
|
|
7476
|
-
path: [...
|
|
7476
|
+
path: [...path41, "args", "monitor_name"],
|
|
7477
7477
|
message: runMonitorCalleeMessage(callee.reason)
|
|
7478
7478
|
});
|
|
7479
7479
|
}
|
|
@@ -7533,11 +7533,11 @@ function insertNoteTemplateMessage(reason) {
|
|
|
7533
7533
|
return `insert_note's template is longer than the ${MONITOR_NOTE_TEMPLATE_MAX}-character limit.`;
|
|
7534
7534
|
}
|
|
7535
7535
|
}
|
|
7536
|
-
function insertNoteArgumentIssues(action,
|
|
7536
|
+
function insertNoteArgumentIssues(action, path41) {
|
|
7537
7537
|
const result = readInsertNoteTemplate(action);
|
|
7538
7538
|
if (result.ok) return [];
|
|
7539
7539
|
return [{
|
|
7540
|
-
path: [...
|
|
7540
|
+
path: [...path41, "args", "template"],
|
|
7541
7541
|
message: insertNoteTemplateMessage(result.reason)
|
|
7542
7542
|
}];
|
|
7543
7543
|
}
|
|
@@ -7634,8 +7634,8 @@ function evalInitialStateError(input) {
|
|
|
7634
7634
|
const parsed = evalInitialStateEntry.safeParse(entries[i]);
|
|
7635
7635
|
if (!parsed.success) {
|
|
7636
7636
|
const issue = parsed.error.issues[0];
|
|
7637
|
-
const
|
|
7638
|
-
return `initial_state[${i}].${
|
|
7637
|
+
const path41 = issue?.path.join(".") || "?";
|
|
7638
|
+
return `initial_state[${i}].${path41} is invalid: ${issue?.message ?? "malformed"}`;
|
|
7639
7639
|
}
|
|
7640
7640
|
if (seenSlugs.has(parsed.data.slug)) {
|
|
7641
7641
|
return `initial_state[${i}].slug "${parsed.data.slug}" is declared more than once`;
|
|
@@ -7859,10 +7859,10 @@ function refineHubAsCodeEvals(config, ctx) {
|
|
|
7859
7859
|
function refineHubAsCodeEvalAttachments(config, ctx) {
|
|
7860
7860
|
const cfg = config;
|
|
7861
7861
|
const referencedHashes = /* @__PURE__ */ new Set();
|
|
7862
|
-
const addTurnIssue = (
|
|
7862
|
+
const addTurnIssue = (path41, turn, name) => {
|
|
7863
7863
|
const error = evalTurnAttachmentsError(turn);
|
|
7864
7864
|
if (error) {
|
|
7865
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path:
|
|
7865
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path41, message: `${name}: ${error}` });
|
|
7866
7866
|
return;
|
|
7867
7867
|
}
|
|
7868
7868
|
for (const hash of collectTurnAttachmentHashes(turn)) referencedHashes.add(hash);
|
|
@@ -7937,17 +7937,17 @@ function refineHubAsCodeCallOpenings(config, ctx) {
|
|
|
7937
7937
|
const agent = entry;
|
|
7938
7938
|
const openings = agent?.call_openings;
|
|
7939
7939
|
if (openings === void 0 || openings === null) return;
|
|
7940
|
-
const
|
|
7940
|
+
const path41 = ["agents", a, "call_openings"];
|
|
7941
7941
|
const name = `agent "${typeof agent?.name === "string" ? agent.name : "<unnamed>"}"`;
|
|
7942
7942
|
if (typeof openings !== "object" || Array.isArray(openings)) {
|
|
7943
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path:
|
|
7943
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path41, message: `${name}: call_openings must map a language to an opening` });
|
|
7944
7944
|
return;
|
|
7945
7945
|
}
|
|
7946
7946
|
if (agent?.role !== "pilot_voice" && Object.keys(openings).length > 0) {
|
|
7947
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path:
|
|
7947
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path41, message: `${name}: only a pilot_voice agent has call_openings` });
|
|
7948
7948
|
}
|
|
7949
7949
|
for (const [language, raw] of Object.entries(openings)) {
|
|
7950
|
-
const at = [...
|
|
7950
|
+
const at = [...path41, language];
|
|
7951
7951
|
if (!callOpeningLanguage.safeParse(language).success) {
|
|
7952
7952
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: at, message: `${name}: call_openings language must be one of ${CALL_OPENING_LANGUAGES.join(", ")}` });
|
|
7953
7953
|
continue;
|
|
@@ -8074,11 +8074,11 @@ function refineHubAsCodeDelegation(config, ctx) {
|
|
|
8074
8074
|
}
|
|
8075
8075
|
}
|
|
8076
8076
|
if (del.context_boundary === void 0) return;
|
|
8077
|
-
const
|
|
8077
|
+
const path41 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
|
|
8078
8078
|
if (del.type !== "hub") {
|
|
8079
8079
|
ctx.addIssue({
|
|
8080
8080
|
code: external_exports.ZodIssueCode.custom,
|
|
8081
|
-
path:
|
|
8081
|
+
path: path41,
|
|
8082
8082
|
message: `context_boundary applies only to \`type: hub\` delegations (got type "${String(del.type)}")`
|
|
8083
8083
|
});
|
|
8084
8084
|
return;
|
|
@@ -8086,7 +8086,7 @@ function refineHubAsCodeDelegation(config, ctx) {
|
|
|
8086
8086
|
if (!CONTEXT_BOUNDARIES.includes(del.context_boundary)) {
|
|
8087
8087
|
ctx.addIssue({
|
|
8088
8088
|
code: external_exports.ZodIssueCode.custom,
|
|
8089
|
-
path:
|
|
8089
|
+
path: path41,
|
|
8090
8090
|
message: `context_boundary must be one of: ${CONTEXT_BOUNDARIES.join(", ")}`
|
|
8091
8091
|
});
|
|
8092
8092
|
}
|
|
@@ -9723,7 +9723,7 @@ var init_chunk_46WU2TD4 = __esm({
|
|
|
9723
9723
|
}
|
|
9724
9724
|
});
|
|
9725
9725
|
|
|
9726
|
-
// ../../packages/core/dist/chunk-
|
|
9726
|
+
// ../../packages/core/dist/chunk-5RGL5RXH.js
|
|
9727
9727
|
function cacheReconcileIdCount(body) {
|
|
9728
9728
|
return body.conversations.length + body.histories.reduce((n, history) => n + history.conversation_ids.length, 0) + body.threads.length;
|
|
9729
9729
|
}
|
|
@@ -9769,23 +9769,29 @@ function parseCommunityLessonVideoUrl(url) {
|
|
|
9769
9769
|
const parts = /^https?:\/\/([^/?#]+)([^?#]*)(?:\?([^#]*))?(?:#.*)?$/i.exec(url.trim());
|
|
9770
9770
|
if (!parts) return null;
|
|
9771
9771
|
const host = parts[1].toLowerCase();
|
|
9772
|
-
const
|
|
9772
|
+
const path41 = parts[2] ?? "";
|
|
9773
9773
|
const query = parts[3] ?? "";
|
|
9774
9774
|
let id;
|
|
9775
9775
|
if (host === YOUTU_BE) {
|
|
9776
|
-
id = /^\/([A-Za-z0-9_-]{11})\/?$/.exec(
|
|
9776
|
+
id = /^\/([A-Za-z0-9_-]{11})\/?$/.exec(path41)?.[1];
|
|
9777
9777
|
} else if (YOUTUBE_HOSTS.has(host)) {
|
|
9778
|
-
if (
|
|
9778
|
+
if (path41 === "/watch" || path41 === "/watch/") {
|
|
9779
9779
|
id = query.split("&").find((param) => param.startsWith("v="))?.slice(2);
|
|
9780
9780
|
} else {
|
|
9781
|
-
id = YOUTUBE_ID_PATH.exec(
|
|
9781
|
+
id = YOUTUBE_ID_PATH.exec(path41)?.[1];
|
|
9782
9782
|
}
|
|
9783
9783
|
}
|
|
9784
9784
|
return id !== void 0 && YOUTUBE_ID.test(id) ? { provider: "youtube", id } : null;
|
|
9785
9785
|
}
|
|
9786
|
-
var connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, PRODUCTION_DIRECT_FIELDS, setProductionCredentialBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, variantAiModeSchema, experimentStatusSchema, overlayUpdateSchema, experimentIdParam, variantIdParam, listExperimentsQuery, listVariantsQuery, listOverridesQuery, deleteOverrideQuery, createExperimentBody, updateExperimentBody, setExperimentStatusBody, createVariantBody, updateVariantBody, upsertOverrideBody, orgTagIdParam, listOrgTagsQuery, ORG_TAG_NAME_REGEX, ORG_TAG_NAME_MAX_LENGTH, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, BASE_CREDENTIAL_MANAGED_BY, BASE_CREDENTIAL_NAME_MAX, baseCredentialNameSchema, createBaseCredentialLinkBody, baseCredentialLinkParams, baseCredentialLinkQuery, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlTable, analyticsSqlSchemaQuery, getConversationsQuery, getMessagesQuery, appMessageSenderType, appMessageReceiverType, apiChannelMessage, apiChannelMessageBody, appMessageBody, appMessageResponse, CALL_STATUSES, callStatus, CALL_END_REASONS, callEndReason, CALL_TRANSPORTS, callTransport, CALL_PARTICIPANT_TYPES, callParticipantType, callInstant, MAX_SDP_OFFER_LENGTH, sdpDescription, sdpOffer, MAX_CALL_REQUEST_BODY_BYTES, hubScoped, callSummaryFields, callSummary, callIdParam, createCallBody, createCallResponse, callReadyBody, callHangupBody, callStatusQuery, MAX_REPORTED_DELEGATION_IDS, callDelegationId, callDelegationsBody, callResponse, MAX_CALL_RECORDING_GAPS, callRecordingGap, callRecordingNoteMetadata, evalCallInstant, EVAL_CALL_MIN_SECONDS, EVAL_CALL_MAX_SECONDS, EVAL_CALL_SPOKEN_LINE_MAX_CHARS, hubScoped2, createEvalCallBody, createEvalCallResponse, evalCallConversationQuery, evalCallRecordQuery, evalCallSpeaker, evalCallUtterance, evalCallTurn, evalCallRecording, evalCallRecordResponse, evalCallFinishResponse, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, recordAiDisclosureAcceptanceBody, activityQuery, deletionModeSchema, deletionRequestBody, CACHE_RECONCILE_MAX_IDS, CACHE_RECONCILE_MAX_HUBS, cacheReconcileScopeSchema, cachedConversationPageSchema, cachedHistoryThreadSchema, cachedPeopleThreadSchema, cacheReconcileBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, conversationHubQuery, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, deleteUserConversationsResponse, sendMessageBody, listConversationsQuery, flagSourceSchema, getConversationsResponse, getConversationDetailParams, getConversationDetailQuery, getConversationDetailResponse, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, copilotSuggestBody, copilotSuggestResponse, CONSULT_THREAD_STATUSES, consultThreadStatus, CONSULT_THREAD_TITLE_MAX, consultThreadTitle, consultThreadSummary, listConsultThreadsQuery, listConsultThreadsResponse, createConsultThreadBody, createConsultThreadResponse, consultThreadIdParam, updateConsultThreadBody, updateConsultThreadResponse, CONSULT_AWAIT_TIMEOUT_MS, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, planKeySchema, billingStatusSchema, billingPlanTypeSchema, dataUsageCountersSchema, REKOR_ENTITLEMENT_CONTRACT_VERSION, entitlementProjectionSchema, rekorEntitlementRefreshMessageSchema, downloadBody, uploadQuery, SIGN_URL_MAX_EXPIRES_IN_SECONDS, signUrlBody, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, CONTACT_ENVIRONMENTS, CONTACT_NAME_MAX, CONTACT_EMAIL_MAX, CONTACT_PHONE_INPUT_MAX, CONTACT_INSTAGRAM_SID_MAX, CONTACT_METADATA_MAX_BYTES, CONTACT_LABEL_MAX, CONTACT_LABELS_MAX, CONTACT_SCOPE_TAGS_MAX, CONTACT_PAGE_MAX, CONTACT_IMPORT_BATCH_MAX, CONTACT_SEARCH_MAX, CONTACT_LIST_NAME_MAX, CONTACT_LIST_NAME_REGEX, CONTACT_LIST_DESCRIPTION_MAX, CONTACT_LIST_FILTER_MAX, CONTACT_LIST_PINS_PER_REQUEST_MAX, SUPPRESSION_SCOPES, SUPPRESSION_ID_REGEX, contactName, contactPhone, contactEmail, contactInstagramSid, contactLabel, contactLabels, contactMetadata, scopeTagIds, contactVisibilitySchema, hasIdentity, IDENTITY_REQUIRED, contactBookOrgQuery, contactBookContactParam, contactBookListParam, contactBookHubParam, contactBookPageQuery, listContactBookContactsQuery, listHubContactBookContactsQuery, contactBookLinkCandidatesQuery, listContactBookListMembersQuery, contactRowFields, createContactBookContactBody, updateContactBookContactBody, contactImportRowSchema, importContactBookContactsBody, linkContactBookContactBody, listName, contactListFilterSchema, createContactBookListBody, updateContactBookListBody, updateContactBookListPinsBody, suppressionIdentitySchema, createContactBookSuppressionBody, contactBookSuppressionParam, listContactBookSuppressionsQuery, CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH, contactBookEmailUnsubscribeTokenSchema, contactBookEmailUnsubscribeQuery, orgListReferenceSchema, hubListNameSchema, HUB_LIST_FILTER_REQUIRED, hubContactListFilterSchema, hubContactListIdParam, hubContactListHubQuery, description, createHubContactListBody, updateHubContactListBody, AUTOMATION_TRIGGER_TYPES, AUTOMATION_EVENT_TYPES, AUTOMATION_TARGET_TYPES, AUTOMATION_ACTION_TYPES, AUTOMATION_CHANNEL_TYPES, AUTOMATION_PURPOSES, AUTOMATION_NAME_MAX, AUTOMATION_DESCRIPTION_MAX, AUTOMATION_INSTRUCTIONS_MAX, AUTOMATION_TEXT_MAX, AUTOMATION_CRON_MAX, AUTOMATION_WEBHOOK_AUTHS, AUTOMATION_WEBHOOK_HEADERS, AUTOMATION_WEBHOOK_BODY_MAX_BYTES, AUTOMATION_WEBHOOK_HEADER_NAME_MAX, AUTOMATION_WEBHOOK_MOUNT, WEBHOOK_RESERVED_HEADERS, automationTriggerSchema, automationTargetSchema, automationActionSchema, automationGateSchema, automationName, automationDescription, automationIdParam, automationHubQuery, automationWebhookParams, AUTOMATION_RUNS_PAGE_MAX, listAutomationRunsQuery, createAutomationBody, updateAutomationBody, AUTOMATION_ALERT_CATEGORIES, AUTOMATION_BLOCKED_ALERT_CATEGORY, automationEventSubscriberParam, VERSION_PART, MOBILE_APP_VERSION_RE, mobileAppVersionSchema, MOBILE_PLATFORMS, mobilePlatformSchema, mobileVersionPolicyQuery, mobileVersionPolicySchema, mobileVersionPolicyResponse, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, PRICING_PLAN_ID_RE, adminPlanIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, MAX_VOICE_CALL_MINUTE_OPS, voiceCallMinuteOps, AUTOMATION_GATE_EVALUATIONS_PER_OPERATION_BOUND, automationGateEvaluationsPerOperation, AUTOMATION_RUN_RETENTION_DAYS_MAX, automationRunRetentionDays, updatePlatformConfigBody, updateFreeOrgLimitBody, adminScopedTargetShape, adminScopedTargetBody, BRANCHING_ORPHAN_ENTITY_TYPES, BRANCHING_ORPHAN_MAX_TARGETS, adminRepairBranchingOrphansBody, adminRepairLegacyOrgGrantsBody, adminRepairCompedPlansBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysBody, dataExplorerKvKeyBody, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteBody, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, sandboxEgressPolicy, SANDBOX_EXEC_MAX_CMD_LENGTH, SANDBOX_EXEC_MAX_ALLOWLIST, SANDBOX_EXEC_MAX_TIMEOUT_MS, adminSandboxExecBody, META_ENTITY_ID_PATTERN, metaEntityId, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, passwordLoginBody, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, reportMessageSchema, sentryRefStatusSchema, createReportBody, editReportBody, reporterReportSummarySchema, reporterListResponseSchema, getReportResponseSchema, reporterTransitionResponseSchema, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, invitePreviewQuery, inviteRolePreviewResponse, inviteContactPreviewResponse, invitePreviewResponse, inviteUnsubscribeBody, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, ADMIN_SKILL_NAME_REGEX, adminSkillNameParam, REKOR_ACTOR_TYPE_HEADER, REKOR_ACTOR_ID_HEADER, REKOR_ACTOR_LABEL_HEADER, REKOR_ACTOR_ORG_HEADER, REKOR_ACTOR_ADMIN_HEADER, REKOR_CLIENT_HEADER, REKOR_TOOL_ID_HEADER, REKOR_CLIENTS, rekorClientSchema, MAX_REKOR_TOOL_ID_LENGTH, rekorToolIdSchema, DATA_PROXY_MOUNT, DATA_PROXY_PREFIX, REKOR_V1_PREFIX, DATA_PROXY_ORG_QUERY_PARAM, MAX_PROVISIONING_BODY_BYTES, dataProxyQuery, rekorAttestation, REQUIRED_REKOR_ATTESTATION_HEADERS, BASE_DESTRUCTION_MOUNT, BASE_DESTRUCTION_PREFIX, BASE_DESTRUCTION_CONFIRM_PATH, baseDestructionConfirmBody, BASE_DESTRUCTION_GRACE_MS, BASE_DESTRUCTION_CONFIRM_TTL_MS, BASE_DESTRUCTION_ORG_QUERY_PARAM, baseDestructionQuery, baseDestructionParam, baseDestructionInitiateBody, baseDestructionStatus, baseDestructionRequest, baseDestructionListResponse, baseDestructionInitiateResponse, baseDestructionConfirmResponse, baseDestructionCancelResponse, rekorBaseChangeMessageSchema, PEOPLE_CONTACT_PAGE_MAX, PEOPLE_CONTACT_PAGE_DEFAULT, PEOPLE_CONTACT_EMAIL_MAX_LENGTH, peopleUserIdSchema, peopleInviteEmailSchema, peopleContactInviteBody, peopleContactInviteIdParam, peopleContactUserIdParam, peopleContactBlockBody, peopleContactPageQuery, peopleOutgoingInviteSchema, peopleContactPersonSchema, peopleContactSchema, peopleIncomingInviteSchema, peopleBlockedUserSchema, PEOPLE_THREAD_MESSAGE_MAX_LENGTH, PEOPLE_THREAD_MESSAGES_PAGE_MAX, PEOPLE_THREAD_PAGE_MAX, PEOPLE_THREAD_PAGE_DEFAULT, PEOPLE_GROUP_NAME_MAX_LENGTH, PEOPLE_GROUP_AVATAR_URL_MAX_LENGTH, PEOPLE_THREAD_ATTACHMENT_MAX_BYTES, PEOPLE_THREAD_ATTACHMENT_NAME_MAX_LENGTH, PEOPLE_ATTACHMENT_STORAGE_PER_USER_BYTES, PEOPLE_THREAD_ATTACHMENT_MAX_MB, PEOPLE_THREAD_ATTACHMENT_URL_TTL_SECONDS, PEOPLE_THREAD_ATTACHMENT_URLS_MAX, peopleThreadIdParam, peopleThreadMessageIdParam, peopleThreadListQuery, peopleThreadMessagesQuery, peopleThreadAttachmentUploadSchema, peopleThreadSendBody, peopleThreadAttachmentUrlsBody, peopleThreadReadBody, peopleThreadSettingsBody, peopleGroupNameSchema, peopleGroupAvatarUrlSchema, peopleGroupCreateBody, peopleOrgDirectBody, peopleGroupUpdateBody, peopleGroupInviteBody, peopleOrgErasureBody, peopleGroupLeaveBody, peopleGroupInviteIdParam, peopleGroupIncomingInviteIdParam, peopleGroupMemberParam, peopleThreadKindSchema, peopleThreadMemberSchema, peopleThreadLastMessageSchema, peopleGroupRoleSchema, peopleGroupSummarySchema, peopleThreadSummarySchema, peopleThreadAttachmentSchema, peopleThreadMessageSchema, peopleGroupMemberSchema, peopleGroupSentInviteSchema, peopleGroupIncomingInviteSchema, PEOPLE_REPORT_DETAILS_MAX_LENGTH, peopleReportReasonSchema, reportDetailsSchema, reportFields, peopleReportBody, PEOPLE_MODERATION_NOTE_MAX_LENGTH, ADMIN_PEOPLE_REPORTS_PAGE_MAX, ADMIN_PEOPLE_REPORTS_PAGE_DEFAULT, peopleModerationActionSchema, moderationNoteSchema, adminPeopleReportListQuery, adminPeopleReportIdParam, adminPeopleMessageRemovalBody, adminPeopleSuspendBody, adminPeopleNoteBody, COMMUNITIES_PER_ORG_MAX, COMMUNITY_NAME_MAX_LENGTH, COMMUNITY_REGISTRY_CLOSED_REASON, COMMUNITY_JOIN_SOURCE_MAX_LENGTH, COMMUNITY_JOIN_FLOW_TOKEN_MAX_LENGTH, COMMUNITY_ROOMS_MAX, COMMUNITY_ROOM_NAME_MAX_LENGTH, COMMUNITY_MESSAGE_MAX_LENGTH, COMMUNITY_MESSAGES_PAGE_SIZE, COMMUNITY_ATTACHMENT_URLS_MAX, COMMUNITY_POST_TITLE_MAX_LENGTH, COMMUNITY_POST_SEARCH_MAX_LENGTH, COMMUNITY_MEMBER_STATUSES, COMMUNITY_RULES_MAX_LENGTH, COMMUNITY_SLOW_MODE_MAX_SECONDS, COMMUNITY_COURSES_LABEL_MAX_LENGTH, COMMUNITY_LIVE_TICKET_PREFIX, COMMUNITY_STATS_DAYS, communityNameSchema, adminCreateCommunityBody, communityIdParam, communityJoinTokenSchema, communityJoinSourceSchema, communityJoinPreviewQuery, communityJoinBody, communityRoomKindSchema, communityRoomNameSchema, communityRoomIdParam, communityMessageIdParam, communityRoomCreateBody, communityRoomUpdateBody, communityMessagesQuery, communityMessageBodySchema, communityPostTitleSchema, communityAttachmentUploadSchema, communitySendBody, communityEditBody, communityPostIdParam, communityPostsQuery, communityAnswerBody, communityReadBody, communityAttachmentUrlsBody, communityPersonParam, communityOrgParam, communityPeopleQuery, communityCoursesLabelSchema, communitySettingsBody, communityBanBody, communityLockBody, communityPinBody, communityModLogQuery, communityReportsQuery, communityReportIdParam, communityLiveTicketSchema, communityLiveQuery, communityMuteBody, communityEmailUnsubscribeTokenSchema, COMMUNITY_EMAIL_UNSUBSCRIBE_PATH, communityEmailUnsubscribeQuery, COMMUNITY_COURSE_TITLE_MAX_LENGTH, COMMUNITY_COURSE_DESCRIPTION_MAX_LENGTH, COMMUNITY_LESSON_BODY_MAX_LENGTH, COMMUNITY_LESSON_LINKS_MAX, COMMUNITY_LESSON_URL_MAX_LENGTH, YOUTUBE_ID, YOUTUBE_HOSTS, YOUTU_BE, YOUTUBE_ID_PATH, communityCourseIdParam, communityCourseModuleIdParam, communityLessonIdParam, communityCourseTitleSchema, communityCourseDescriptionSchema, communityCoursePositionSchema, communityLessonVideoUrlSchema, communityLessonLinkSchema, communityLessonLinksSchema, communityLessonBodySchema, communityCourseCreateBody, communityCourseUpdateBody, communityModuleCreateBody, communityModuleUpdateBody, communityLessonCreateBody, communityLessonUpdateBody, COMMUNITY_LESSON_TREE_MAX_BYTES, COMMUNITY_LESSON_LINK_PROTOCOLS, LINK_SCHEME, adminSetGuideHubBody, GUIDE_HUB_INELIGIBLE_REASON;
|
|
9787
|
-
|
|
9788
|
-
|
|
9786
|
+
function communityLessonVideoWatchUrl(video) {
|
|
9787
|
+
switch (video.provider) {
|
|
9788
|
+
case "youtube":
|
|
9789
|
+
return `https://www.youtube.com/watch?v=${video.id}`;
|
|
9790
|
+
}
|
|
9791
|
+
}
|
|
9792
|
+
var connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, PRODUCTION_DIRECT_FIELDS, setProductionCredentialBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, variantAiModeSchema, experimentStatusSchema, overlayUpdateSchema, experimentIdParam, variantIdParam, listExperimentsQuery, listVariantsQuery, listOverridesQuery, deleteOverrideQuery, createExperimentBody, updateExperimentBody, setExperimentStatusBody, createVariantBody, updateVariantBody, upsertOverrideBody, orgTagIdParam, listOrgTagsQuery, ORG_TAG_NAME_REGEX, ORG_TAG_NAME_MAX_LENGTH, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, BASE_CREDENTIAL_MANAGED_BY, BASE_CREDENTIAL_NAME_MAX, baseCredentialNameSchema, createBaseCredentialLinkBody, baseCredentialLinkParams, baseCredentialLinkQuery, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlTable, analyticsSqlSchemaQuery, getConversationsQuery, getMessagesQuery, appMessageSenderType, appMessageReceiverType, apiChannelMessage, apiChannelMessageBody, appMessageBody, appMessageResponse, CALL_STATUSES, callStatus, CALL_END_REASONS, callEndReason, CALL_TRANSPORTS, callTransport, CALL_PARTICIPANT_TYPES, callParticipantType, callInstant, MAX_SDP_OFFER_LENGTH, sdpDescription, sdpOffer, MAX_CALL_REQUEST_BODY_BYTES, hubScoped, callSummaryFields, callSummary, callIdParam, createCallBody, createCallResponse, callReadyBody, callHangupBody, callStatusQuery, MAX_REPORTED_DELEGATION_IDS, callDelegationId, callDelegationsBody, callResponse, MAX_CALL_RECORDING_GAPS, callRecordingGap, callRecordingNoteMetadata, evalCallInstant, EVAL_CALL_MIN_SECONDS, EVAL_CALL_MAX_SECONDS, EVAL_CALL_SPOKEN_LINE_MAX_CHARS, hubScoped2, createEvalCallBody, createEvalCallResponse, evalCallConversationQuery, evalCallRecordQuery, evalCallSpeaker, evalCallUtterance, evalCallTurn, evalCallRecording, evalCallRecordResponse, evalCallFinishResponse, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, recordAiDisclosureAcceptanceBody, activityQuery, deletionModeSchema, deletionRequestBody, CACHE_RECONCILE_MAX_IDS, CACHE_RECONCILE_MAX_HUBS, cacheReconcileScopeSchema, cachedConversationPageSchema, cachedHistoryThreadSchema, cachedPeopleThreadSchema, cacheReconcileBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, conversationHubQuery, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, deleteUserConversationsResponse, sendMessageBody, listConversationsQuery, flagSourceSchema, getConversationsResponse, getConversationDetailParams, getConversationDetailQuery, getConversationDetailResponse, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, copilotSuggestBody, copilotSuggestResponse, CONSULT_THREAD_STATUSES, consultThreadStatus, CONSULT_THREAD_TITLE_MAX, consultThreadTitle, consultThreadSummary, listConsultThreadsQuery, listConsultThreadsResponse, createConsultThreadBody, createConsultThreadResponse, consultThreadIdParam, updateConsultThreadBody, updateConsultThreadResponse, CONSULT_AWAIT_TIMEOUT_MS, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, planKeySchema, billingStatusSchema, billingPlanTypeSchema, dataUsageCountersSchema, REKOR_ENTITLEMENT_CONTRACT_VERSION, entitlementProjectionSchema, rekorEntitlementRefreshMessageSchema, downloadBody, uploadQuery, SIGN_URL_MAX_EXPIRES_IN_SECONDS, signUrlBody, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, CONTACT_ENVIRONMENTS, CONTACT_NAME_MAX, CONTACT_EMAIL_MAX, CONTACT_PHONE_INPUT_MAX, CONTACT_INSTAGRAM_SID_MAX, CONTACT_METADATA_MAX_BYTES, CONTACT_LABEL_MAX, CONTACT_LABELS_MAX, CONTACT_SCOPE_TAGS_MAX, CONTACT_PAGE_MAX, CONTACT_IMPORT_BATCH_MAX, CONTACT_SEARCH_MAX, CONTACT_LIST_NAME_MAX, CONTACT_LIST_NAME_REGEX, CONTACT_LIST_DESCRIPTION_MAX, CONTACT_LIST_FILTER_MAX, CONTACT_LIST_PINS_PER_REQUEST_MAX, SUPPRESSION_SCOPES, SUPPRESSION_ID_REGEX, contactName, contactPhone, contactEmail, contactInstagramSid, contactLabel, contactLabels, contactMetadata, scopeTagIds, contactVisibilitySchema, hasIdentity, IDENTITY_REQUIRED, contactBookOrgQuery, contactBookContactParam, contactBookListParam, contactBookHubParam, contactBookPageQuery, listContactBookContactsQuery, listHubContactBookContactsQuery, contactBookLinkCandidatesQuery, listContactBookListMembersQuery, contactRowFields, createContactBookContactBody, updateContactBookContactBody, contactImportRowSchema, importContactBookContactsBody, linkContactBookContactBody, listName, contactListFilterSchema, createContactBookListBody, updateContactBookListBody, updateContactBookListPinsBody, suppressionIdentitySchema, createContactBookSuppressionBody, contactBookSuppressionParam, listContactBookSuppressionsQuery, CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH, contactBookEmailUnsubscribeTokenSchema, contactBookEmailUnsubscribeQuery, orgListReferenceSchema, hubListNameSchema, HUB_LIST_FILTER_REQUIRED, hubContactListFilterSchema, hubContactListIdParam, hubContactListHubQuery, description, createHubContactListBody, updateHubContactListBody, AUTOMATION_TRIGGER_TYPES, AUTOMATION_EVENT_TYPES, AUTOMATION_TARGET_TYPES, AUTOMATION_ACTION_TYPES, AUTOMATION_CHANNEL_TYPES, AUTOMATION_PURPOSES, AUTOMATION_NAME_MAX, AUTOMATION_DESCRIPTION_MAX, AUTOMATION_INSTRUCTIONS_MAX, AUTOMATION_TEXT_MAX, AUTOMATION_CRON_MAX, AUTOMATION_WEBHOOK_AUTHS, AUTOMATION_WEBHOOK_HEADERS, AUTOMATION_WEBHOOK_BODY_MAX_BYTES, AUTOMATION_WEBHOOK_HEADER_NAME_MAX, AUTOMATION_WEBHOOK_MOUNT, WEBHOOK_RESERVED_HEADERS, automationTriggerSchema, automationTargetSchema, automationActionSchema, automationGateSchema, automationName, automationDescription, automationIdParam, automationHubQuery, automationWebhookParams, AUTOMATION_RUNS_PAGE_MAX, listAutomationRunsQuery, createAutomationBody, updateAutomationBody, AUTOMATION_ALERT_CATEGORIES, AUTOMATION_BLOCKED_ALERT_CATEGORY, automationEventSubscriberParam, VERSION_PART, MOBILE_APP_VERSION_RE, mobileAppVersionSchema, MOBILE_PLATFORMS, mobilePlatformSchema, mobileVersionPolicyQuery, mobileVersionPolicySchema, mobileVersionPolicyResponse, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, PRICING_PLAN_ID_RE, adminPlanIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, MAX_VOICE_CALL_MINUTE_OPS, voiceCallMinuteOps, AUTOMATION_GATE_EVALUATIONS_PER_OPERATION_BOUND, automationGateEvaluationsPerOperation, AUTOMATION_RUN_RETENTION_DAYS_MAX, automationRunRetentionDays, updatePlatformConfigBody, updateFreeOrgLimitBody, adminScopedTargetShape, adminScopedTargetBody, BRANCHING_ORPHAN_ENTITY_TYPES, BRANCHING_ORPHAN_MAX_TARGETS, adminRepairBranchingOrphansBody, adminRepairLegacyOrgGrantsBody, adminRepairCompedPlansBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysBody, dataExplorerKvKeyBody, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteBody, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, sandboxEgressPolicy, SANDBOX_EXEC_MAX_CMD_LENGTH, SANDBOX_EXEC_MAX_ALLOWLIST, SANDBOX_EXEC_MAX_TIMEOUT_MS, adminSandboxExecBody, META_ENTITY_ID_PATTERN, metaEntityId, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, passwordLoginBody, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, reportMessageSchema, sentryRefStatusSchema, createReportBody, editReportBody, reporterReportSummarySchema, reporterListResponseSchema, getReportResponseSchema, reporterTransitionResponseSchema, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, invitePreviewQuery, inviteRolePreviewResponse, inviteContactPreviewResponse, invitePreviewResponse, inviteUnsubscribeBody, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, ADMIN_SKILL_NAME_REGEX, adminSkillNameParam, REKOR_ACTOR_TYPE_HEADER, REKOR_ACTOR_ID_HEADER, REKOR_ACTOR_LABEL_HEADER, REKOR_ACTOR_ORG_HEADER, REKOR_ACTOR_ADMIN_HEADER, REKOR_CLIENT_HEADER, REKOR_TOOL_ID_HEADER, REKOR_CLIENTS, rekorClientSchema, MAX_REKOR_TOOL_ID_LENGTH, rekorToolIdSchema, DATA_PROXY_MOUNT, DATA_PROXY_PREFIX, REKOR_V1_PREFIX, DATA_PROXY_ORG_QUERY_PARAM, MAX_PROVISIONING_BODY_BYTES, dataProxyQuery, rekorAttestation, REQUIRED_REKOR_ATTESTATION_HEADERS, BASE_DESTRUCTION_MOUNT, BASE_DESTRUCTION_PREFIX, BASE_DESTRUCTION_CONFIRM_PATH, baseDestructionConfirmBody, BASE_DESTRUCTION_GRACE_MS, BASE_DESTRUCTION_CONFIRM_TTL_MS, BASE_DESTRUCTION_ORG_QUERY_PARAM, baseDestructionQuery, baseDestructionParam, baseDestructionInitiateBody, baseDestructionStatus, baseDestructionRequest, baseDestructionListResponse, baseDestructionInitiateResponse, baseDestructionConfirmResponse, baseDestructionCancelResponse, rekorBaseChangeMessageSchema, PEOPLE_CONTACT_PAGE_MAX, PEOPLE_CONTACT_PAGE_DEFAULT, PEOPLE_CONTACT_EMAIL_MAX_LENGTH, peopleUserIdSchema, peopleInviteEmailSchema, peopleContactInviteBody, peopleContactInviteIdParam, peopleContactUserIdParam, peopleContactBlockBody, peopleContactPageQuery, peopleOutgoingInviteSchema, peopleContactPersonSchema, peopleContactSchema, peopleIncomingInviteSchema, peopleBlockedUserSchema, PEOPLE_THREAD_MESSAGE_MAX_LENGTH, PEOPLE_THREAD_MESSAGES_PAGE_MAX, PEOPLE_THREAD_PAGE_MAX, PEOPLE_THREAD_PAGE_DEFAULT, PEOPLE_GROUP_NAME_MAX_LENGTH, PEOPLE_GROUP_AVATAR_URL_MAX_LENGTH, PEOPLE_THREAD_ATTACHMENT_MAX_BYTES, PEOPLE_THREAD_ATTACHMENT_NAME_MAX_LENGTH, PEOPLE_ATTACHMENT_STORAGE_PER_USER_BYTES, PEOPLE_THREAD_ATTACHMENT_MAX_MB, PEOPLE_THREAD_ATTACHMENT_URL_TTL_SECONDS, PEOPLE_THREAD_ATTACHMENT_URLS_MAX, peopleThreadIdParam, peopleThreadMessageIdParam, peopleThreadListQuery, peopleThreadMessagesQuery, peopleThreadAttachmentUploadSchema, peopleThreadSendBody, peopleThreadAttachmentUrlsBody, peopleThreadReadBody, peopleThreadSettingsBody, peopleGroupNameSchema, peopleGroupAvatarUrlSchema, peopleGroupCreateBody, peopleOrgDirectBody, peopleGroupUpdateBody, peopleGroupInviteBody, peopleOrgErasureBody, peopleGroupLeaveBody, peopleGroupInviteIdParam, peopleGroupIncomingInviteIdParam, peopleGroupMemberParam, peopleThreadKindSchema, peopleThreadMemberSchema, peopleThreadLastMessageSchema, peopleGroupRoleSchema, peopleGroupSummarySchema, peopleThreadSummarySchema, peopleThreadAttachmentSchema, peopleThreadMessageSchema, peopleGroupMemberSchema, peopleGroupSentInviteSchema, peopleGroupIncomingInviteSchema, PEOPLE_REPORT_DETAILS_MAX_LENGTH, peopleReportReasonSchema, reportDetailsSchema, reportFields, peopleReportBody, PEOPLE_MODERATION_NOTE_MAX_LENGTH, ADMIN_PEOPLE_REPORTS_PAGE_MAX, ADMIN_PEOPLE_REPORTS_PAGE_DEFAULT, peopleModerationActionSchema, moderationNoteSchema, adminPeopleReportListQuery, adminPeopleReportIdParam, adminPeopleMessageRemovalBody, adminPeopleSuspendBody, adminPeopleNoteBody, COMMUNITIES_PER_ORG_MAX, COMMUNITY_NAME_MAX_LENGTH, COMMUNITY_REGISTRY_CLOSED_REASON, COMMUNITY_ADMISSIONS, COMMUNITY_ACCESS_ADD_MAX, COMMUNITY_JOIN_SOURCE_MAX_LENGTH, COMMUNITY_JOIN_FLOW_TOKEN_MAX_LENGTH, COMMUNITY_ROOMS_MAX, COMMUNITY_ROOM_NAME_MAX_LENGTH, COMMUNITY_MESSAGE_MAX_LENGTH, COMMUNITY_MESSAGES_PAGE_SIZE, COMMUNITY_ATTACHMENT_URLS_MAX, COMMUNITY_POST_TITLE_MAX_LENGTH, COMMUNITY_POST_SEARCH_MAX_LENGTH, COMMUNITY_MEMBER_STATUSES, COMMUNITY_RULES_MAX_LENGTH, COMMUNITY_SLOW_MODE_MAX_SECONDS, COMMUNITY_COURSES_LABEL_MAX_LENGTH, COMMUNITY_LIVE_TICKET_PREFIX, COMMUNITY_STATS_DAYS, communityNameSchema, adminCreateCommunityBody, communityIdParam, communityJoinTokenSchema, communityJoinSourceSchema, communityJoinPreviewQuery, communityJoinBody, communityRoomKindSchema, communityRoomNameSchema, communityRoomIdParam, communityMessageIdParam, communityRoomCreateBody, communityRoomUpdateBody, communityMessagesQuery, communityMessageBodySchema, communityPostTitleSchema, communityAttachmentUploadSchema, communitySendBody, communityEditBody, communityPostIdParam, communityPostsQuery, communityAnswerBody, communityReadBody, communityAttachmentUrlsBody, communityPersonParam, communityOrgParam, communityPeopleQuery, communityCoursesLabelSchema, communityAdmissionSchema, communitySettingsBody, communityAccessEmailSchema, communityAccessAddBody, communityAccessQuery, communityAccessIdParam, communityBanBody, communityLockBody, communityPinBody, communityModLogQuery, communityReportsQuery, communityReportIdParam, communityLiveTicketSchema, communityLiveQuery, communityMuteBody, communityEmailUnsubscribeTokenSchema, COMMUNITY_EMAIL_UNSUBSCRIBE_PATH, communityEmailUnsubscribeQuery, COMMUNITY_COURSES_MAX, COMMUNITY_COURSE_MODULES_MAX, COMMUNITY_COURSE_LESSONS_MAX, COMMUNITY_LESSONS_MAX, COMMUNITY_COURSE_TITLE_MAX_LENGTH, COMMUNITY_COURSE_DESCRIPTION_MAX_LENGTH, COMMUNITY_LESSON_BODY_MAX_LENGTH, COMMUNITY_LESSON_LINKS_MAX, COMMUNITY_LESSON_URL_MAX_LENGTH, YOUTUBE_ID, YOUTUBE_HOSTS, YOUTU_BE, YOUTUBE_ID_PATH, communityCourseIdParam, communityCourseModuleIdParam, communityLessonIdParam, communityCourseTitleSchema, communityCourseDescriptionSchema, communityCoursePositionSchema, communityLessonVideoUrlSchema, communityLessonLinkSchema, communityLessonLinksSchema, communityLessonBodySchema, communityCourseCreateBody, communityCourseUpdateBody, communityModuleCreateBody, communityModuleUpdateBody, communityLessonCreateBody, communityLessonUpdateBody, COMMUNITY_LESSON_TREE_MAX_BYTES, COMMUNITY_LESSON_LINK_PROTOCOLS, COMMUNITY_LESSON_TREE_TOO_LARGE_REASON, COMMUNITY_LESSON_TREE_TOO_DEEP_REASON, COMMUNITY_LESSON_LINE_PREFIX_TOO_WIDE_REASON, LINK_SCHEME, adminSetGuideHubBody, GUIDE_HUB_INELIGIBLE_REASON;
|
|
9793
|
+
var init_chunk_5RGL5RXH = __esm({
|
|
9794
|
+
"../../packages/core/dist/chunk-5RGL5RXH.js"() {
|
|
9789
9795
|
"use strict";
|
|
9790
9796
|
init_chunk_VORPHGDT();
|
|
9791
9797
|
init_chunk_POL7B3EM();
|
|
@@ -10846,7 +10852,8 @@ var init_chunk_BMAOKAMB = __esm({
|
|
|
10846
10852
|
});
|
|
10847
10853
|
updateUserBody = external_exports.object({
|
|
10848
10854
|
user_language: external_exports.string().optional(),
|
|
10849
|
-
|
|
10855
|
+
/** true: dark; false: light; null: follow each device's own setting (`AppearanceMode`). */
|
|
10856
|
+
ui_dark_mode: external_exports.boolean().nullable().optional(),
|
|
10850
10857
|
profile_name: external_exports.string().optional(),
|
|
10851
10858
|
link_email_to_identities: external_exports.boolean().optional()
|
|
10852
10859
|
}).refine(
|
|
@@ -12812,6 +12819,8 @@ var init_chunk_BMAOKAMB = __esm({
|
|
|
12812
12819
|
COMMUNITIES_PER_ORG_MAX = 10;
|
|
12813
12820
|
COMMUNITY_NAME_MAX_LENGTH = 100;
|
|
12814
12821
|
COMMUNITY_REGISTRY_CLOSED_REASON = "community_registry_closed";
|
|
12822
|
+
COMMUNITY_ADMISSIONS = ["link", "invite"];
|
|
12823
|
+
COMMUNITY_ACCESS_ADD_MAX = 500;
|
|
12815
12824
|
COMMUNITY_JOIN_SOURCE_MAX_LENGTH = 32;
|
|
12816
12825
|
COMMUNITY_JOIN_FLOW_TOKEN_MAX_LENGTH = 128;
|
|
12817
12826
|
COMMUNITY_ROOMS_MAX = 20;
|
|
@@ -12872,11 +12881,20 @@ var init_chunk_BMAOKAMB = __esm({
|
|
|
12872
12881
|
communityOrgParam = external_exports.object({ orgId: uuidSchema });
|
|
12873
12882
|
communityPeopleQuery = external_exports.object({ after: peopleUserIdSchema.optional() });
|
|
12874
12883
|
communityCoursesLabelSchema = external_exports.string().trim().max(COMMUNITY_COURSES_LABEL_MAX_LENGTH);
|
|
12884
|
+
communityAdmissionSchema = external_exports.enum(COMMUNITY_ADMISSIONS);
|
|
12875
12885
|
communitySettingsBody = external_exports.object({
|
|
12876
12886
|
rules: external_exports.string().trim().max(COMMUNITY_RULES_MAX_LENGTH).nullable().optional().transform((rules) => rules === "" ? null : rules),
|
|
12877
12887
|
slow_mode_seconds: external_exports.number().int().min(0).max(COMMUNITY_SLOW_MODE_MAX_SECONDS).optional(),
|
|
12878
|
-
courses_label: communityCoursesLabelSchema.optional()
|
|
12879
|
-
|
|
12888
|
+
courses_label: communityCoursesLabelSchema.optional(),
|
|
12889
|
+
admission: communityAdmissionSchema.optional()
|
|
12890
|
+
}).strict().refine(
|
|
12891
|
+
(b) => b.rules !== void 0 || b.slow_mode_seconds !== void 0 || b.courses_label !== void 0 || b.admission !== void 0,
|
|
12892
|
+
"nothing to change"
|
|
12893
|
+
);
|
|
12894
|
+
communityAccessEmailSchema = external_exports.string().trim().toLowerCase().max(CONTACT_EMAIL_MAX).email();
|
|
12895
|
+
communityAccessAddBody = external_exports.object({ emails: external_exports.array(communityAccessEmailSchema).min(1).max(COMMUNITY_ACCESS_ADD_MAX) }).strict();
|
|
12896
|
+
communityAccessQuery = external_exports.object({ cursor: external_exports.string().min(1).max(256).optional() });
|
|
12897
|
+
communityAccessIdParam = external_exports.object({ communityId: uuidSchema, accessId: uuidSchema });
|
|
12880
12898
|
communityBanBody = external_exports.object({ delete_messages: external_exports.boolean().optional() }).strict();
|
|
12881
12899
|
communityLockBody = external_exports.object({ locked: external_exports.boolean() }).strict();
|
|
12882
12900
|
communityPinBody = external_exports.object({ pinned: external_exports.boolean() }).strict();
|
|
@@ -12891,6 +12909,10 @@ var init_chunk_BMAOKAMB = __esm({
|
|
|
12891
12909
|
communityEmailUnsubscribeTokenSchema = external_exports.string().max(200).regex(/^[0-9a-f-]{36}\.[A-Za-z0-9_-]{1,64}\.[0-9a-f]{64}$/i);
|
|
12892
12910
|
COMMUNITY_EMAIL_UNSUBSCRIBE_PATH = "/api/communities/email-unsubscribe";
|
|
12893
12911
|
communityEmailUnsubscribeQuery = external_exports.object({ token: communityEmailUnsubscribeTokenSchema });
|
|
12912
|
+
COMMUNITY_COURSES_MAX = 20;
|
|
12913
|
+
COMMUNITY_COURSE_MODULES_MAX = 20;
|
|
12914
|
+
COMMUNITY_COURSE_LESSONS_MAX = 100;
|
|
12915
|
+
COMMUNITY_LESSONS_MAX = 500;
|
|
12894
12916
|
COMMUNITY_COURSE_TITLE_MAX_LENGTH = 200;
|
|
12895
12917
|
COMMUNITY_COURSE_DESCRIPTION_MAX_LENGTH = 2e3;
|
|
12896
12918
|
COMMUNITY_LESSON_BODY_MAX_LENGTH = 2e4;
|
|
@@ -12951,6 +12973,9 @@ var init_chunk_BMAOKAMB = __esm({
|
|
|
12951
12973
|
}).strict().refine((b) => Object.values(b).some((value) => value !== void 0), "nothing to change");
|
|
12952
12974
|
COMMUNITY_LESSON_TREE_MAX_BYTES = 512 * 1024;
|
|
12953
12975
|
COMMUNITY_LESSON_LINK_PROTOCOLS = ["http", "https", "mailto"];
|
|
12976
|
+
COMMUNITY_LESSON_TREE_TOO_LARGE_REASON = "lesson_tree_too_large";
|
|
12977
|
+
COMMUNITY_LESSON_TREE_TOO_DEEP_REASON = "lesson_tree_too_deep";
|
|
12978
|
+
COMMUNITY_LESSON_LINE_PREFIX_TOO_WIDE_REASON = "lesson_line_prefix_too_wide";
|
|
12954
12979
|
LINK_SCHEME = new RegExp(`^(${COMMUNITY_LESSON_LINK_PROTOCOLS.join("|")}):`, "i");
|
|
12955
12980
|
adminSetGuideHubBody = external_exports.object({
|
|
12956
12981
|
hub_id: uuidSchema,
|
|
@@ -13262,7 +13287,7 @@ var init_chunk_BPDC67ML = __esm({
|
|
|
13262
13287
|
var init_contracts = __esm({
|
|
13263
13288
|
"../../packages/core/dist/contracts/index.js"() {
|
|
13264
13289
|
"use strict";
|
|
13265
|
-
|
|
13290
|
+
init_chunk_5RGL5RXH();
|
|
13266
13291
|
init_chunk_VORPHGDT();
|
|
13267
13292
|
init_chunk_POL7B3EM();
|
|
13268
13293
|
init_chunk_46WU2TD4();
|
|
@@ -13304,10 +13329,10 @@ function toDataProxyPath(v1Path) {
|
|
|
13304
13329
|
}
|
|
13305
13330
|
return `${DATA_PROXY_PREFIX}${v1Path.slice(REKOR_V1_PREFIX.length)}`;
|
|
13306
13331
|
}
|
|
13307
|
-
function withOrgSelector(
|
|
13308
|
-
if (!orgId) return
|
|
13309
|
-
const separator =
|
|
13310
|
-
return `${
|
|
13332
|
+
function withOrgSelector(path41, orgId) {
|
|
13333
|
+
if (!orgId) return path41;
|
|
13334
|
+
const separator = path41.includes("?") ? "&" : "?";
|
|
13335
|
+
return `${path41}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
|
|
13311
13336
|
}
|
|
13312
13337
|
function dataErrorMessage(err) {
|
|
13313
13338
|
if (!(err instanceof ApiError)) return null;
|
|
@@ -13323,7 +13348,7 @@ function dataErrorMessage(err) {
|
|
|
13323
13348
|
const message = parsed?.error?.message;
|
|
13324
13349
|
return typeof message === "string" && message ? message : null;
|
|
13325
13350
|
}
|
|
13326
|
-
var RETRYABLE_BACKOFF_MS, delay, ApiError, ApiClient;
|
|
13351
|
+
var RETRYABLE_BACKOFF_MS, delay, courseAuthoring, ApiError, ApiClient;
|
|
13327
13352
|
var init_api_client = __esm({
|
|
13328
13353
|
"src/lib/api-client.ts"() {
|
|
13329
13354
|
"use strict";
|
|
@@ -13332,6 +13357,7 @@ var init_api_client = __esm({
|
|
|
13332
13357
|
init_mask_secrets();
|
|
13333
13358
|
RETRYABLE_BACKOFF_MS = [500, 1e3, 2e3];
|
|
13334
13359
|
delay = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
13360
|
+
courseAuthoring = (communityId, courseId) => `/api/communities/${encodeURIComponent(communityId)}/authoring/courses${courseId === void 0 ? "" : `/${encodeURIComponent(courseId)}`}`;
|
|
13335
13361
|
ApiError = class extends Error {
|
|
13336
13362
|
status;
|
|
13337
13363
|
body;
|
|
@@ -13341,13 +13367,13 @@ var init_api_client = __esm({
|
|
|
13341
13367
|
* telling them apart by body shape misreads one for the other.
|
|
13342
13368
|
*/
|
|
13343
13369
|
path;
|
|
13344
|
-
constructor(method,
|
|
13370
|
+
constructor(method, path41, status, body) {
|
|
13345
13371
|
const safeBody = maskSecretsInMessage(body);
|
|
13346
|
-
super(`API request failed: ${method} ${
|
|
13372
|
+
super(`API request failed: ${method} ${path41} (${status}): ${safeBody}`);
|
|
13347
13373
|
this.name = "ApiError";
|
|
13348
13374
|
this.status = status;
|
|
13349
13375
|
this.body = safeBody;
|
|
13350
|
-
this.path =
|
|
13376
|
+
this.path = path41;
|
|
13351
13377
|
}
|
|
13352
13378
|
/** True when the status code is a 4xx client error (expected user-facing condition, not a bug). */
|
|
13353
13379
|
get isExpected() {
|
|
@@ -13491,6 +13517,33 @@ var init_api_client = __esm({
|
|
|
13491
13517
|
body
|
|
13492
13518
|
);
|
|
13493
13519
|
}
|
|
13520
|
+
// A community's courses, which `wayai pull`/`push communities/<x>` sync (gh #5003): every course,
|
|
13521
|
+
// drafts included; a course whole in one read (its export); and the authoring writes. A create
|
|
13522
|
+
// with a `client_id` already used answers the course, module or lesson made then, and makes none.
|
|
13523
|
+
async listCommunityCourseDrafts(communityId) {
|
|
13524
|
+
return this.request("GET", courseAuthoring(communityId));
|
|
13525
|
+
}
|
|
13526
|
+
async exportCommunityCourse(communityId, courseId) {
|
|
13527
|
+
return this.request("GET", `${courseAuthoring(communityId, courseId)}/export`);
|
|
13528
|
+
}
|
|
13529
|
+
async createCommunityCourse(communityId, body) {
|
|
13530
|
+
return this.request("POST", courseAuthoring(communityId), body);
|
|
13531
|
+
}
|
|
13532
|
+
async updateCommunityCourse(communityId, courseId, body) {
|
|
13533
|
+
return this.request("PATCH", courseAuthoring(communityId, courseId), body);
|
|
13534
|
+
}
|
|
13535
|
+
async createCommunityModule(communityId, courseId, body) {
|
|
13536
|
+
return this.request("POST", `${courseAuthoring(communityId, courseId)}/modules`, body);
|
|
13537
|
+
}
|
|
13538
|
+
async updateCommunityModule(communityId, courseId, moduleId, body) {
|
|
13539
|
+
return this.request("PATCH", `${courseAuthoring(communityId, courseId)}/modules/${encodeURIComponent(moduleId)}`, body);
|
|
13540
|
+
}
|
|
13541
|
+
async createCommunityLesson(communityId, courseId, body) {
|
|
13542
|
+
return this.request("POST", `${courseAuthoring(communityId, courseId)}/lessons`, body);
|
|
13543
|
+
}
|
|
13544
|
+
async updateCommunityLesson(communityId, courseId, lessonId, body) {
|
|
13545
|
+
return this.request("PATCH", `${courseAuthoring(communityId, courseId)}/lessons/${encodeURIComponent(lessonId)}`, body);
|
|
13546
|
+
}
|
|
13494
13547
|
// Read which hub is the WayAI Guide (platform-admin gated, wayai#4948). Used by
|
|
13495
13548
|
// `wayai admin guide status`.
|
|
13496
13549
|
async adminGetGuide() {
|
|
@@ -13519,8 +13572,8 @@ var init_api_client = __esm({
|
|
|
13519
13572
|
...opts?.check && { check: true }
|
|
13520
13573
|
});
|
|
13521
13574
|
}
|
|
13522
|
-
async lookup(
|
|
13523
|
-
const params = new URLSearchParams({ path:
|
|
13575
|
+
async lookup(path41, opts) {
|
|
13576
|
+
const params = new URLSearchParams({ path: path41 });
|
|
13524
13577
|
if (opts?.organizationId) params.set("organization_id", opts.organizationId);
|
|
13525
13578
|
return this.request("GET", `/api/ci/lookup?${params.toString()}`);
|
|
13526
13579
|
}
|
|
@@ -14028,9 +14081,9 @@ var init_api_client = __esm({
|
|
|
14028
14081
|
* sandbox for this conversation, or the blob was purged).
|
|
14029
14082
|
*/
|
|
14030
14083
|
async downloadArchiveSandboxFs(hubId, conversationId) {
|
|
14031
|
-
const
|
|
14032
|
-
addApiBreadcrumb("GET",
|
|
14033
|
-
const url = `${this.apiUrl}${
|
|
14084
|
+
const path41 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
|
|
14085
|
+
addApiBreadcrumb("GET", path41);
|
|
14086
|
+
const url = `${this.apiUrl}${path41}`;
|
|
14034
14087
|
let response = await this.send(url, "GET");
|
|
14035
14088
|
if (response.status === 401 && this.onUnauthorized) {
|
|
14036
14089
|
let refreshed;
|
|
@@ -14044,7 +14097,7 @@ var init_api_client = __esm({
|
|
|
14044
14097
|
}
|
|
14045
14098
|
}
|
|
14046
14099
|
if (!response.ok) {
|
|
14047
|
-
throw new ApiError("GET",
|
|
14100
|
+
throw new ApiError("GET", path41, response.status, await response.text());
|
|
14048
14101
|
}
|
|
14049
14102
|
return new Uint8Array(await response.arrayBuffer());
|
|
14050
14103
|
}
|
|
@@ -14090,9 +14143,9 @@ var init_api_client = __esm({
|
|
|
14090
14143
|
`/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
|
|
14091
14144
|
);
|
|
14092
14145
|
}
|
|
14093
|
-
async request(method,
|
|
14094
|
-
addApiBreadcrumb(method,
|
|
14095
|
-
const url = `${this.apiUrl}${
|
|
14146
|
+
async request(method, path41, body, extraHeaders, contentType) {
|
|
14147
|
+
addApiBreadcrumb(method, path41);
|
|
14148
|
+
const url = `${this.apiUrl}${path41}`;
|
|
14096
14149
|
let refreshedOn401 = false;
|
|
14097
14150
|
for (let retry = 0; ; retry++) {
|
|
14098
14151
|
let response = await this.send(url, method, body, extraHeaders, contentType);
|
|
@@ -14117,7 +14170,7 @@ var init_api_client = __esm({
|
|
|
14117
14170
|
await delay(RETRYABLE_BACKOFF_MS[retry]);
|
|
14118
14171
|
continue;
|
|
14119
14172
|
}
|
|
14120
|
-
throw new ApiError(method,
|
|
14173
|
+
throw new ApiError(method, path41, response.status, errorBody);
|
|
14121
14174
|
}
|
|
14122
14175
|
}
|
|
14123
14176
|
/**
|
|
@@ -14179,9 +14232,9 @@ var init_api_client = __esm({
|
|
|
14179
14232
|
* transient-retry loop — re-streaming a blob is not worth a cold-start probe.
|
|
14180
14233
|
*/
|
|
14181
14234
|
async dataDownload(v1Path, orgId) {
|
|
14182
|
-
const
|
|
14183
|
-
addApiBreadcrumb("GET",
|
|
14184
|
-
const url = `${this.apiUrl}${
|
|
14235
|
+
const path41 = withOrgSelector(toDataProxyPath(v1Path), orgId);
|
|
14236
|
+
addApiBreadcrumb("GET", path41);
|
|
14237
|
+
const url = `${this.apiUrl}${path41}`;
|
|
14185
14238
|
let response = await this.send(url, "GET");
|
|
14186
14239
|
if (response.status === 401 && this.onUnauthorized) {
|
|
14187
14240
|
let refreshed;
|
|
@@ -14195,7 +14248,7 @@ var init_api_client = __esm({
|
|
|
14195
14248
|
}
|
|
14196
14249
|
}
|
|
14197
14250
|
if (!response.ok) {
|
|
14198
|
-
throw new ApiError("GET",
|
|
14251
|
+
throw new ApiError("GET", path41, response.status, await response.text());
|
|
14199
14252
|
}
|
|
14200
14253
|
return {
|
|
14201
14254
|
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
@@ -14266,6 +14319,15 @@ function extractApiErrorCode(err) {
|
|
|
14266
14319
|
return null;
|
|
14267
14320
|
}
|
|
14268
14321
|
}
|
|
14322
|
+
function extractApiErrorReason(err) {
|
|
14323
|
+
if (!(err instanceof ApiError)) return null;
|
|
14324
|
+
try {
|
|
14325
|
+
const reason = JSON.parse(err.body).details?.reason;
|
|
14326
|
+
return typeof reason === "string" ? reason : null;
|
|
14327
|
+
} catch {
|
|
14328
|
+
return null;
|
|
14329
|
+
}
|
|
14330
|
+
}
|
|
14269
14331
|
function extractApiRetryAfterSeconds(err) {
|
|
14270
14332
|
if (!(err instanceof ApiError)) return null;
|
|
14271
14333
|
try {
|
|
@@ -14432,7 +14494,7 @@ var init_dist = __esm({
|
|
|
14432
14494
|
"../../packages/core/dist/index.js"() {
|
|
14433
14495
|
"use strict";
|
|
14434
14496
|
init_chunk_WSROLRJN();
|
|
14435
|
-
|
|
14497
|
+
init_chunk_5RGL5RXH();
|
|
14436
14498
|
init_chunk_VORPHGDT();
|
|
14437
14499
|
init_chunk_POL7B3EM();
|
|
14438
14500
|
init_chunk_46WU2TD4();
|
|
@@ -15105,6 +15167,12 @@ function writeFileNoFollow(root, abs, data) {
|
|
|
15105
15167
|
fs.writeFileSync(abs, data);
|
|
15106
15168
|
return true;
|
|
15107
15169
|
}
|
|
15170
|
+
function writeFileIfChangedNoFollow(root, abs, content) {
|
|
15171
|
+
const bytes = Buffer.from(content, "utf-8");
|
|
15172
|
+
if (fileHasBytes(abs, bytes)) return false;
|
|
15173
|
+
if (!writeFileNoFollow(root, abs, bytes)) throw symlinkRefusal(path.relative(path.dirname(root), abs), "file");
|
|
15174
|
+
return true;
|
|
15175
|
+
}
|
|
15108
15176
|
function createFileNoFollow(root, abs, data) {
|
|
15109
15177
|
if (!ensureRealSubdirNoSymlink(root, path.dirname(abs), true)) return "refused";
|
|
15110
15178
|
try {
|
|
@@ -15597,14 +15665,14 @@ function readOrgFields(doc) {
|
|
|
15597
15665
|
if (name) config.organization_name = name;
|
|
15598
15666
|
return { kind: "declared", config };
|
|
15599
15667
|
}
|
|
15600
|
-
function declarationFrom(
|
|
15601
|
-
if (
|
|
15602
|
-
if (
|
|
15603
|
-
return readOrgFields(
|
|
15668
|
+
function declarationFrom(load14) {
|
|
15669
|
+
if (load14.kind === "absent") return { kind: "none" };
|
|
15670
|
+
if (load14.kind === "malformed") return { kind: "invalid", detail: `it does not parse (${load14.reason})` };
|
|
15671
|
+
return readOrgFields(load14.doc);
|
|
15604
15672
|
}
|
|
15605
|
-
function legacyFieldsIn(
|
|
15606
|
-
if (
|
|
15607
|
-
return LEGACY_FIELDS.filter((f) =>
|
|
15673
|
+
function legacyFieldsIn(load14) {
|
|
15674
|
+
if (load14.kind !== "ok") return [];
|
|
15675
|
+
return LEGACY_FIELDS.filter((f) => load14.doc[f] !== void 0);
|
|
15608
15676
|
}
|
|
15609
15677
|
function resolve3(gitRoot) {
|
|
15610
15678
|
const manifestFile = workspaceManifestPath(gitRoot);
|
|
@@ -15790,13 +15858,13 @@ function writeRepoConfig(config, root) {
|
|
|
15790
15858
|
if (stale.kind === "declared" && stale.config.organization_id !== config.organization_id) {
|
|
15791
15859
|
throw expected(rebindRefusal(config.organization_id, stale.config.organization_id));
|
|
15792
15860
|
}
|
|
15793
|
-
const
|
|
15794
|
-
if (
|
|
15861
|
+
const load14 = loadWorkspaceManifest(gitRoot);
|
|
15862
|
+
if (load14.kind === "malformed") {
|
|
15795
15863
|
throw expected(
|
|
15796
|
-
`Cannot write the organization binding: ${WORKSPACE_MANIFEST_LABEL} does not parse (${
|
|
15864
|
+
`Cannot write the organization binding: ${WORKSPACE_MANIFEST_LABEL} does not parse (${load14.reason}). Fix or delete it, then re-run.`
|
|
15797
15865
|
);
|
|
15798
15866
|
}
|
|
15799
|
-
const { organization_id: _id, organization_name: _name, ...rest } =
|
|
15867
|
+
const { organization_id: _id, organization_name: _name, ...rest } = load14.kind === "ok" ? load14.doc : {};
|
|
15800
15868
|
const doc = { organization_id: config.organization_id };
|
|
15801
15869
|
if (config.organization_name?.trim()) doc.organization_name = config.organization_name.trim();
|
|
15802
15870
|
Object.assign(doc, rest);
|
|
@@ -16354,9 +16422,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
|
|
|
16354
16422
|
}
|
|
16355
16423
|
function readVersionCache(filename = CLI_CACHE_FILE) {
|
|
16356
16424
|
try {
|
|
16357
|
-
const
|
|
16358
|
-
if (!existsSync3(
|
|
16359
|
-
const parsed = JSON.parse(readFileSync5(
|
|
16425
|
+
const path41 = getVersionCachePath(filename);
|
|
16426
|
+
if (!existsSync3(path41)) return null;
|
|
16427
|
+
const parsed = JSON.parse(readFileSync5(path41, "utf-8"));
|
|
16360
16428
|
if (typeof parsed.lastCheck !== "number") return null;
|
|
16361
16429
|
if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
|
|
16362
16430
|
return parsed;
|
|
@@ -16376,10 +16444,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
|
|
|
16376
16444
|
return Date.now() - cache.lastCheck > maxAgeMs;
|
|
16377
16445
|
}
|
|
16378
16446
|
function writeVersionCache(filename, cache) {
|
|
16379
|
-
const
|
|
16380
|
-
const dir = dirname6(
|
|
16447
|
+
const path41 = getVersionCachePath(filename);
|
|
16448
|
+
const dir = dirname6(path41);
|
|
16381
16449
|
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
16382
|
-
writeFileSync2(
|
|
16450
|
+
writeFileSync2(path41, JSON.stringify(cache));
|
|
16383
16451
|
}
|
|
16384
16452
|
function touchVersionCache(filename) {
|
|
16385
16453
|
writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
|
|
@@ -16418,14 +16486,14 @@ function parseFrontmatterVersion(content) {
|
|
|
16418
16486
|
function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
|
|
16419
16487
|
const found = [];
|
|
16420
16488
|
for (const rel of paths) {
|
|
16421
|
-
const
|
|
16422
|
-
if (!existsSync4(
|
|
16489
|
+
const path41 = join8(projectRoot, rel);
|
|
16490
|
+
if (!existsSync4(path41)) continue;
|
|
16423
16491
|
let version = null;
|
|
16424
16492
|
try {
|
|
16425
|
-
version = parseFrontmatterVersion(readFileSync6(
|
|
16493
|
+
version = parseFrontmatterVersion(readFileSync6(path41, "utf-8"));
|
|
16426
16494
|
} catch {
|
|
16427
16495
|
}
|
|
16428
|
-
found.push({ path:
|
|
16496
|
+
found.push({ path: path41, version });
|
|
16429
16497
|
}
|
|
16430
16498
|
return found;
|
|
16431
16499
|
}
|
|
@@ -17957,18 +18025,22 @@ var init_init = __esm({
|
|
|
17957
18025
|
import * as fs9 from "fs";
|
|
17958
18026
|
import * as path11 from "path";
|
|
17959
18027
|
import * as yaml6 from "js-yaml";
|
|
17960
|
-
function
|
|
17961
|
-
|
|
18028
|
+
function uuidField(doc, key) {
|
|
18029
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
18030
|
+
const id = doc[key];
|
|
18031
|
+
return typeof id === "string" && UUID_RE3.test(id) ? id : null;
|
|
18032
|
+
}
|
|
18033
|
+
function readYamlIdNoFollow(root, file, key) {
|
|
18034
|
+
const bytes = readFileNoFollow(root, file);
|
|
17962
18035
|
if (bytes === null) return null;
|
|
17963
|
-
let doc;
|
|
17964
18036
|
try {
|
|
17965
|
-
|
|
18037
|
+
return uuidField(yaml6.load(bytes.toString("utf-8")), key);
|
|
17966
18038
|
} catch {
|
|
17967
18039
|
return null;
|
|
17968
18040
|
}
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
return
|
|
18041
|
+
}
|
|
18042
|
+
function readCommunityId(folder) {
|
|
18043
|
+
return readYamlIdNoFollow(folder, path11.join(folder, COMMUNITY_FILE), "community_id");
|
|
17972
18044
|
}
|
|
17973
18045
|
function listCommunityFolders(communitiesDir) {
|
|
17974
18046
|
let entries;
|
|
@@ -19606,9 +19678,9 @@ var init_base_workspace = __esm({
|
|
|
19606
19678
|
// src/lib/subtree-routing.ts
|
|
19607
19679
|
import * as path20 from "path";
|
|
19608
19680
|
function readRepoDefaults(gitRoot) {
|
|
19609
|
-
const
|
|
19610
|
-
if (
|
|
19611
|
-
const raw =
|
|
19681
|
+
const load14 = loadWorkspaceManifest(gitRoot);
|
|
19682
|
+
if (load14.kind !== "ok") return {};
|
|
19683
|
+
const raw = load14.doc;
|
|
19612
19684
|
const str = (v) => {
|
|
19613
19685
|
if (typeof v !== "string" || !v.trim()) return void 0;
|
|
19614
19686
|
const value = v.trim();
|
|
@@ -19893,12 +19965,12 @@ async function createDataClient(orgId) {
|
|
|
19893
19965
|
return orgId2;
|
|
19894
19966
|
}
|
|
19895
19967
|
return {
|
|
19896
|
-
async request(method,
|
|
19897
|
-
const envelope = await api.dataRequest(method,
|
|
19968
|
+
async request(method, path41, body) {
|
|
19969
|
+
const envelope = await api.dataRequest(method, path41, body, org);
|
|
19898
19970
|
return envelope.data;
|
|
19899
19971
|
},
|
|
19900
|
-
async requestRaw(method,
|
|
19901
|
-
const body_ = await api.dataRequest(method,
|
|
19972
|
+
async requestRaw(method, path41, body) {
|
|
19973
|
+
const body_ = await api.dataRequest(method, path41, body, org);
|
|
19902
19974
|
return body_;
|
|
19903
19975
|
},
|
|
19904
19976
|
async collectPages(makePath) {
|
|
@@ -20742,6 +20814,133 @@ var init_sync = __esm({
|
|
|
20742
20814
|
}
|
|
20743
20815
|
});
|
|
20744
20816
|
|
|
20817
|
+
// src/lib/eval-session-control.ts
|
|
20818
|
+
function formatEvalSessionStopCommand(sessionId, hubId) {
|
|
20819
|
+
return `wayai eval session stop ${sessionId} --hub ${hubId}`;
|
|
20820
|
+
}
|
|
20821
|
+
function formatEvalResultsCommand(sessionId, hubId) {
|
|
20822
|
+
return `wayai eval-results --session ${sessionId} --hub ${hubId}`;
|
|
20823
|
+
}
|
|
20824
|
+
function describeSeedRelease(status) {
|
|
20825
|
+
if (status === "pending") {
|
|
20826
|
+
return "A turn that started before the stop is still finishing \u2014 its fixture stays reserved until that turn settles.";
|
|
20827
|
+
}
|
|
20828
|
+
if (status === "releasing") return "Its fixture is being released.";
|
|
20829
|
+
return "A turn already in progress may still be finishing \u2014 wait for it before starting another run.";
|
|
20830
|
+
}
|
|
20831
|
+
function classifyStopOutcome(response) {
|
|
20832
|
+
return response.data.stopped ? { kind: "cancelled", seedRelease: response.data.seed_release_status } : { kind: "already_finished", status: response.data.session.session_status };
|
|
20833
|
+
}
|
|
20834
|
+
function isTerminalStopFailure(err) {
|
|
20835
|
+
return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 408 && err.status !== 429;
|
|
20836
|
+
}
|
|
20837
|
+
async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
20838
|
+
const attempts = opts.attempts ?? STOP_ATTEMPTS;
|
|
20839
|
+
const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
|
|
20840
|
+
const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
|
|
20841
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
20842
|
+
let lastError = new Error("Stop was never attempted");
|
|
20843
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20844
|
+
let timeout;
|
|
20845
|
+
try {
|
|
20846
|
+
return await Promise.race([
|
|
20847
|
+
stop(),
|
|
20848
|
+
new Promise((_resolve, reject) => {
|
|
20849
|
+
timeout = setTimeout(
|
|
20850
|
+
() => reject(new Error(`Stop attempt ${attempt} timed out after ${attemptTimeoutMs}ms`)),
|
|
20851
|
+
attemptTimeoutMs
|
|
20852
|
+
);
|
|
20853
|
+
})
|
|
20854
|
+
]);
|
|
20855
|
+
} catch (err) {
|
|
20856
|
+
lastError = err;
|
|
20857
|
+
if (isTerminalStopFailure(err)) break;
|
|
20858
|
+
} finally {
|
|
20859
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
20860
|
+
}
|
|
20861
|
+
const delay2 = backoffMs[attempt - 1];
|
|
20862
|
+
if (attempt < attempts && delay2) await sleep3(delay2);
|
|
20863
|
+
}
|
|
20864
|
+
throw lastError;
|
|
20865
|
+
}
|
|
20866
|
+
function installEvalSignalHandlers(input) {
|
|
20867
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
20868
|
+
const manualStopCommand = formatEvalSessionStopCommand(input.sessionId, input.hubId);
|
|
20869
|
+
let signalCount = 0;
|
|
20870
|
+
let disposed = false;
|
|
20871
|
+
let settle;
|
|
20872
|
+
const settled = new Promise((resolve12) => {
|
|
20873
|
+
settle = resolve12;
|
|
20874
|
+
});
|
|
20875
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
20876
|
+
const dispose = () => {
|
|
20877
|
+
if (disposed) return;
|
|
20878
|
+
disposed = true;
|
|
20879
|
+
for (const [signal, listener] of listeners) process.off(signal, listener);
|
|
20880
|
+
};
|
|
20881
|
+
const finish = (exitCode) => {
|
|
20882
|
+
dispose();
|
|
20883
|
+
settle();
|
|
20884
|
+
exit(exitCode);
|
|
20885
|
+
};
|
|
20886
|
+
const handle = (signal) => {
|
|
20887
|
+
signalCount += 1;
|
|
20888
|
+
const signalExitCode = SIGNAL_EXIT_CODE[signal];
|
|
20889
|
+
if (signalCount > 1) {
|
|
20890
|
+
console.error("\nSecond signal received; exiting immediately. The remote eval session may still be running.");
|
|
20891
|
+
console.error(`Run: ${manualStopCommand}`);
|
|
20892
|
+
finish(signalExitCode);
|
|
20893
|
+
return;
|
|
20894
|
+
}
|
|
20895
|
+
console.error(`
|
|
20896
|
+
${signal} received; stopping remote eval session ${input.sessionId}...`);
|
|
20897
|
+
void stopEvalSessionWithRetries(input.stop).then((response) => {
|
|
20898
|
+
if (signalCount > 1) return;
|
|
20899
|
+
const outcome = classifyStopOutcome(response);
|
|
20900
|
+
if (outcome.kind === "already_finished") {
|
|
20901
|
+
console.error(`Session had already finished (${outcome.status}) \u2014 nothing was cancelled.`);
|
|
20902
|
+
console.error(`See its results: ${formatEvalResultsCommand(input.sessionId, input.hubId)}`);
|
|
20903
|
+
} else {
|
|
20904
|
+
console.error("Remote session cancellation requested.");
|
|
20905
|
+
console.error(describeSeedRelease(outcome.seedRelease));
|
|
20906
|
+
}
|
|
20907
|
+
finish(signalExitCode);
|
|
20908
|
+
}).catch((err) => {
|
|
20909
|
+
if (signalCount > 1) return;
|
|
20910
|
+
console.error(`Failed to confirm remote cancellation: ${extractApiMessage(err)}`);
|
|
20911
|
+
console.error(`The session may still be running. Run: ${manualStopCommand}`);
|
|
20912
|
+
finish(1);
|
|
20913
|
+
});
|
|
20914
|
+
};
|
|
20915
|
+
for (const signal of HANDLED_SIGNALS) {
|
|
20916
|
+
const listener = () => handle(signal);
|
|
20917
|
+
listeners.set(signal, listener);
|
|
20918
|
+
process.on(signal, listener);
|
|
20919
|
+
}
|
|
20920
|
+
return {
|
|
20921
|
+
manualStopCommand,
|
|
20922
|
+
async interrupted() {
|
|
20923
|
+
if (signalCount === 0) return false;
|
|
20924
|
+
await settled;
|
|
20925
|
+
return true;
|
|
20926
|
+
},
|
|
20927
|
+
dispose
|
|
20928
|
+
};
|
|
20929
|
+
}
|
|
20930
|
+
var SIGNAL_EXIT_CODE, HANDLED_SIGNALS, STOP_ATTEMPTS, STOP_ATTEMPT_TIMEOUT_MS, STOP_BACKOFF_MS;
|
|
20931
|
+
var init_eval_session_control = __esm({
|
|
20932
|
+
"src/lib/eval-session-control.ts"() {
|
|
20933
|
+
"use strict";
|
|
20934
|
+
init_api_client();
|
|
20935
|
+
init_errors2();
|
|
20936
|
+
SIGNAL_EXIT_CODE = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
|
|
20937
|
+
HANDLED_SIGNALS = Object.keys(SIGNAL_EXIT_CODE);
|
|
20938
|
+
STOP_ATTEMPTS = 3;
|
|
20939
|
+
STOP_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
20940
|
+
STOP_BACKOFF_MS = [500, 1e3];
|
|
20941
|
+
}
|
|
20942
|
+
});
|
|
20943
|
+
|
|
20745
20944
|
// src/communities/api.ts
|
|
20746
20945
|
async function communitiesClient() {
|
|
20747
20946
|
const { config, accessToken } = await requireAuth();
|
|
@@ -20874,6 +21073,23 @@ function settingsContent(settings) {
|
|
|
20874
21073
|
function roomContent(room) {
|
|
20875
21074
|
return { name: toLf(room.name).trim(), archived: room.archived };
|
|
20876
21075
|
}
|
|
21076
|
+
function courseContent(course) {
|
|
21077
|
+
return { title: toLf(course.title).trim(), description: toLf(course.description).trim(), published: course.published };
|
|
21078
|
+
}
|
|
21079
|
+
function moduleContent(module) {
|
|
21080
|
+
return { title: toLf(module.title).trim() };
|
|
21081
|
+
}
|
|
21082
|
+
function lessonContent(lesson) {
|
|
21083
|
+
return {
|
|
21084
|
+
title: toLf(lesson.title).trim(),
|
|
21085
|
+
body: toLf(lesson.body),
|
|
21086
|
+
video: lesson.video ? { provider: lesson.video.provider, id: lesson.video.id } : null,
|
|
21087
|
+
links: lesson.links.map((link) => ({ title: toLf(link.title).trim(), url: link.url.trim() })),
|
|
21088
|
+
room_id: lesson.room_id,
|
|
21089
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
21090
|
+
published: lesson.published
|
|
21091
|
+
};
|
|
21092
|
+
}
|
|
20877
21093
|
function rev(parts) {
|
|
20878
21094
|
return `${REV_VERSION}:${createHash3("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 12)}`;
|
|
20879
21095
|
}
|
|
@@ -20885,15 +21101,31 @@ function serverRoom(room) {
|
|
|
20885
21101
|
const content = roomContent(room);
|
|
20886
21102
|
return { content, rev: roomRev(room.room_id, content) };
|
|
20887
21103
|
}
|
|
21104
|
+
function serverCourse(course) {
|
|
21105
|
+
const content = courseContent(course);
|
|
21106
|
+
return { content, rev: courseRev(course.course_id, content) };
|
|
21107
|
+
}
|
|
21108
|
+
function serverModule(module) {
|
|
21109
|
+
const content = moduleContent(module);
|
|
21110
|
+
return { content, rev: moduleRev(module.module_id, content) };
|
|
21111
|
+
}
|
|
21112
|
+
function serverLesson(lesson) {
|
|
21113
|
+
const content = lessonContent(lesson);
|
|
21114
|
+
return { content, rev: lessonRev(lesson.lesson_id, content) };
|
|
21115
|
+
}
|
|
21116
|
+
function knownBase(base) {
|
|
21117
|
+
return base !== void 0 && base.startsWith(`${REV_VERSION}:`) ? base : void 0;
|
|
21118
|
+
}
|
|
20888
21119
|
function classify(base, file, server) {
|
|
20889
|
-
const known = base
|
|
21120
|
+
const known = knownBase(base);
|
|
20890
21121
|
if (known === void 0) return file === server ? "converged" : "conflict";
|
|
20891
|
-
|
|
20892
|
-
|
|
21122
|
+
return threeWay(file !== known, server !== known, file === server);
|
|
21123
|
+
}
|
|
21124
|
+
function threeWay(fileChanged, serverChanged, agree) {
|
|
20893
21125
|
if (!fileChanged && !serverChanged) return "in_sync";
|
|
20894
|
-
if (
|
|
21126
|
+
if (!serverChanged) return "local";
|
|
20895
21127
|
if (!fileChanged) return "remote";
|
|
20896
|
-
return
|
|
21128
|
+
return agree ? "converged" : "conflict";
|
|
20897
21129
|
}
|
|
20898
21130
|
function createClientId(communityId, kind, localName2) {
|
|
20899
21131
|
return uuidV5(communityId, `${kind}:${localName2}`);
|
|
@@ -20904,7 +21136,7 @@ function localName(name, id, taken, fallback) {
|
|
|
20904
21136
|
const withPrefix = `${slug}--${id.slice(0, 8)}`;
|
|
20905
21137
|
return taken.has(withPrefix) ? `${slug}--${id}` : withPrefix;
|
|
20906
21138
|
}
|
|
20907
|
-
var REV_VERSION, settingsRev, joinLinkRev, roomRev;
|
|
21139
|
+
var REV_VERSION, settingsRev, joinLinkRev, roomRev, courseRev, moduleRev, lessonRev;
|
|
20908
21140
|
var init_model = __esm({
|
|
20909
21141
|
"src/communities/model.ts"() {
|
|
20910
21142
|
"use strict";
|
|
@@ -20913,10 +21145,25 @@ var init_model = __esm({
|
|
|
20913
21145
|
settingsRev = (communityId, c) => rev(["settings", communityId, c.rules, c.slow_mode_seconds, c.courses_label]);
|
|
20914
21146
|
joinLinkRev = (communityId, c) => rev(["join_link", communityId, c.enabled]);
|
|
20915
21147
|
roomRev = (roomId, c) => rev(["room", roomId, c.name, c.archived]);
|
|
21148
|
+
courseRev = (courseId, c) => rev(["course", courseId, c.title, c.description, c.published]);
|
|
21149
|
+
moduleRev = (moduleId, c) => rev(["module", moduleId, c.title]);
|
|
21150
|
+
lessonRev = (lessonId, c) => rev([
|
|
21151
|
+
"lesson",
|
|
21152
|
+
lessonId,
|
|
21153
|
+
c.title,
|
|
21154
|
+
c.body,
|
|
21155
|
+
c.video?.provider ?? null,
|
|
21156
|
+
c.video?.id ?? null,
|
|
21157
|
+
c.links.map((link) => [link.title, link.url]),
|
|
21158
|
+
c.room_id,
|
|
21159
|
+
c.requires_paid_plan,
|
|
21160
|
+
c.published
|
|
21161
|
+
]);
|
|
20916
21162
|
}
|
|
20917
21163
|
});
|
|
20918
21164
|
|
|
20919
|
-
// src/communities/files.ts
|
|
21165
|
+
// src/communities/course-files.ts
|
|
21166
|
+
import * as fs18 from "fs";
|
|
20920
21167
|
import * as path24 from "path";
|
|
20921
21168
|
import * as yaml12 from "js-yaml";
|
|
20922
21169
|
function isMapping(value) {
|
|
@@ -20933,21 +21180,359 @@ function optionalString(doc, key, where, problems2) {
|
|
|
20933
21180
|
if (typeof value !== "string") problems2.push(`${where}: ${key} must be text`);
|
|
20934
21181
|
return typeof value === "string" ? value : void 0;
|
|
20935
21182
|
}
|
|
21183
|
+
function optionalBoolean(doc, key, where, problems2) {
|
|
21184
|
+
const value = doc[key];
|
|
21185
|
+
if (value === void 0 || value === null) return false;
|
|
21186
|
+
if (typeof value !== "boolean") problems2.push(`${where}: ${key} must be true or false`);
|
|
21187
|
+
return value === true;
|
|
21188
|
+
}
|
|
21189
|
+
function identity(doc, what, where, problems2) {
|
|
21190
|
+
const id = optionalString(doc, "id", where, problems2);
|
|
21191
|
+
if (id !== void 0 && !UUID_RE3.test(id)) problems2.push(`${where}: id must be ${what}'s id (a UUID)`);
|
|
21192
|
+
const rev2 = optionalString(doc, "rev", where, problems2);
|
|
21193
|
+
if (id === void 0 && rev2 !== void 0) problems2.push(`${where}: a rev without an id \u2014 remove the rev to create the ${what}`);
|
|
21194
|
+
return { ...id === void 0 ? {} : { id }, ...rev2 === void 0 ? {} : { rev: rev2 } };
|
|
21195
|
+
}
|
|
20936
21196
|
function contractProblem(error) {
|
|
20937
21197
|
const [issue] = error.issues;
|
|
20938
21198
|
return issue.path.length > 0 ? `${issue.path.map(String).join(".")}: ${issue.message}` : issue.message;
|
|
20939
21199
|
}
|
|
21200
|
+
function readIdList(raw, where, problems2) {
|
|
21201
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
21202
|
+
if (!Array.isArray(raw) || raw.some((id) => typeof id !== "string" || !UUID_RE3.test(id)) || new Set(raw).size !== raw.length) {
|
|
21203
|
+
problems2.push(`${where} must be a list of ids, each once \u2014 leave it as pull and push write it`);
|
|
21204
|
+
return void 0;
|
|
21205
|
+
}
|
|
21206
|
+
return raw;
|
|
21207
|
+
}
|
|
21208
|
+
function readCourseSynced(raw, where, problems2) {
|
|
21209
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
21210
|
+
if (!isMapping(raw)) {
|
|
21211
|
+
problems2.push(`${where}: synced must be a mapping`);
|
|
21212
|
+
return void 0;
|
|
21213
|
+
}
|
|
21214
|
+
unknownKeys(raw, COURSE_SYNCED_KEYS, `${where} synced`, problems2);
|
|
21215
|
+
const lessons = {};
|
|
21216
|
+
if (raw.lessons !== void 0 && raw.lessons !== null) {
|
|
21217
|
+
if (!isMapping(raw.lessons)) problems2.push(`${where}: synced.lessons must be a mapping of module ids`);
|
|
21218
|
+
else {
|
|
21219
|
+
for (const [moduleId, ids] of Object.entries(raw.lessons)) {
|
|
21220
|
+
if (!UUID_RE3.test(moduleId)) problems2.push(`${where}: synced.lessons: ${JSON.stringify(moduleId)} is not a module id`);
|
|
21221
|
+
const list = readIdList(ids, `${where} synced.lessons.${moduleId}`, problems2);
|
|
21222
|
+
if (list) lessons[moduleId] = list;
|
|
21223
|
+
}
|
|
21224
|
+
}
|
|
21225
|
+
}
|
|
21226
|
+
const modules = readIdList(raw.modules, `${where} synced.modules`, problems2);
|
|
21227
|
+
return { ...modules === void 0 ? {} : { modules }, lessons };
|
|
21228
|
+
}
|
|
21229
|
+
function readNameList(raw, where, what, problems2) {
|
|
21230
|
+
if (raw === void 0 || raw === null) return [];
|
|
21231
|
+
if (!Array.isArray(raw)) {
|
|
21232
|
+
problems2.push(`${where} must be a list of ${what} names`);
|
|
21233
|
+
return [];
|
|
21234
|
+
}
|
|
21235
|
+
const names = [];
|
|
21236
|
+
for (const name of raw) {
|
|
21237
|
+
if (typeof name !== "string" || !isPathSafeId(name)) {
|
|
21238
|
+
problems2.push(`${where}: ${JSON.stringify(name)} is not a ${what} name \u2014 ${PATH_SAFE_ID_RULE}`);
|
|
21239
|
+
} else if (names.includes(name)) {
|
|
21240
|
+
problems2.push(`${where}: ${name} is listed twice`);
|
|
21241
|
+
} else {
|
|
21242
|
+
names.push(name);
|
|
21243
|
+
}
|
|
21244
|
+
}
|
|
21245
|
+
return names;
|
|
21246
|
+
}
|
|
21247
|
+
function readCourseFolders(communityFolder, slugs, problems2) {
|
|
21248
|
+
const coursesDir = path24.join(communityFolder, COURSES_DIR);
|
|
21249
|
+
requireRealSubdirNoSymlink(communityFolder, coursesDir, false);
|
|
21250
|
+
const courses = [];
|
|
21251
|
+
for (const slug of slugs) {
|
|
21252
|
+
const folder = path24.join(coursesDir, slug);
|
|
21253
|
+
requireRealSubdirNoSymlink(communityFolder, folder, false);
|
|
21254
|
+
if (!isRealDirectory(folder)) {
|
|
21255
|
+
problems2.push(`${COMMUNITY_FILE} courses: ${slug} has no folder ${COURSES_DIR}/${slug}/`);
|
|
21256
|
+
continue;
|
|
21257
|
+
}
|
|
21258
|
+
const course = readCourseFolder(communityFolder, folder, slug, problems2);
|
|
21259
|
+
if (course) courses.push(course);
|
|
21260
|
+
}
|
|
21261
|
+
const unlisted = folderEntries(coursesDir).filter((name) => !slugs.includes(name) && isRealDirectory(path24.join(coursesDir, name))).map((name) => ({ name, id: readYamlIdNoFollow(communityFolder, path24.join(coursesDir, name, COURSE_FILE), "id") }));
|
|
21262
|
+
return { courses, unlisted };
|
|
21263
|
+
}
|
|
21264
|
+
function readCourseFolder(communityFolder, folder, slug, problems2) {
|
|
21265
|
+
const where = `${COURSES_DIR}/${slug}/${COURSE_FILE}`;
|
|
21266
|
+
const text = readRealFileOrThrow(communityFolder, path24.join(folder, COURSE_FILE));
|
|
21267
|
+
if (text === null) {
|
|
21268
|
+
problems2.push(`${COURSES_DIR}/${slug}/: no ${COURSE_FILE}`);
|
|
21269
|
+
return null;
|
|
21270
|
+
}
|
|
21271
|
+
let doc;
|
|
21272
|
+
try {
|
|
21273
|
+
doc = yaml12.load(text);
|
|
21274
|
+
} catch (err) {
|
|
21275
|
+
problems2.push(`${where} is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
21276
|
+
return null;
|
|
21277
|
+
}
|
|
21278
|
+
if (!isMapping(doc)) {
|
|
21279
|
+
problems2.push(`${where} must be a YAML mapping`);
|
|
21280
|
+
return null;
|
|
21281
|
+
}
|
|
21282
|
+
unknownKeys(doc, COURSE_KEYS, where, problems2);
|
|
21283
|
+
const { id, rev: rev2 } = identity(doc, "a course", where, problems2);
|
|
21284
|
+
const synced2 = readCourseSynced(doc.synced, where, problems2);
|
|
21285
|
+
if (id === void 0 && synced2 !== void 0) problems2.push(`${where}: synced without an id \u2014 remove it to create the course`);
|
|
21286
|
+
const description2 = doc.description ?? "";
|
|
21287
|
+
const parsed = communityCourseCreateBody.safeParse({ title: doc.title, description: description2 });
|
|
21288
|
+
if (!parsed.success) problems2.push(`${where}: ${contractProblem(parsed.error)}`);
|
|
21289
|
+
const published = optionalBoolean(doc, "published", where, problems2);
|
|
21290
|
+
const lessonsDir = path24.join(folder, LESSONS_DIR);
|
|
21291
|
+
requireRealSubdirNoSymlink(communityFolder, lessonsDir, false);
|
|
21292
|
+
const modules = readModules(communityFolder, lessonsDir, doc.modules, slug, where, problems2);
|
|
21293
|
+
const listed2 = new Set(modules.flatMap((module) => module.lessons.map((lesson) => lesson.slug)));
|
|
21294
|
+
const unlisted = folderEntries(lessonsDir).filter((name) => name.endsWith(LESSON_FILE_EXT) && !listed2.has(path24.basename(name, LESSON_FILE_EXT))).map((name) => ({ name, id: uuidField(lessonFrontmatter(readFileNoFollow(communityFolder, path24.join(lessonsDir, name))), "id") }));
|
|
21295
|
+
if (!parsed.success) return null;
|
|
21296
|
+
return {
|
|
21297
|
+
slug,
|
|
21298
|
+
...id === void 0 ? {} : { id },
|
|
21299
|
+
...rev2 === void 0 ? {} : { rev: rev2 },
|
|
21300
|
+
title: doc.title,
|
|
21301
|
+
description: description2,
|
|
21302
|
+
published,
|
|
21303
|
+
modules,
|
|
21304
|
+
...synced2 === void 0 ? {} : { synced: synced2 },
|
|
21305
|
+
unlisted
|
|
21306
|
+
};
|
|
21307
|
+
}
|
|
21308
|
+
function readModules(communityFolder, lessonsDir, raw, courseSlug, courseWhere, problems2) {
|
|
21309
|
+
if (raw === void 0 || raw === null) return [];
|
|
21310
|
+
if (!Array.isArray(raw)) {
|
|
21311
|
+
problems2.push(`${courseWhere}: modules must be a list`);
|
|
21312
|
+
return [];
|
|
21313
|
+
}
|
|
21314
|
+
const modules = [];
|
|
21315
|
+
const keys = /* @__PURE__ */ new Set();
|
|
21316
|
+
const listedLessons = /* @__PURE__ */ new Set();
|
|
21317
|
+
raw.forEach((entry, index) => {
|
|
21318
|
+
const at = `${courseWhere} modules[${index}]`;
|
|
21319
|
+
if (!isMapping(entry)) {
|
|
21320
|
+
problems2.push(`${at}: must be a mapping`);
|
|
21321
|
+
return;
|
|
21322
|
+
}
|
|
21323
|
+
unknownKeys(entry, MODULE_KEYS, at, problems2);
|
|
21324
|
+
const key = entry.key;
|
|
21325
|
+
if (typeof key !== "string" || !isPathSafeId(key)) {
|
|
21326
|
+
problems2.push(`${at}: key must be a plain name \u2014 ${PATH_SAFE_ID_RULE}`);
|
|
21327
|
+
return;
|
|
21328
|
+
}
|
|
21329
|
+
const where = `${courseWhere} module ${key}`;
|
|
21330
|
+
if (keys.has(key)) problems2.push(`${where}: the key is used twice`);
|
|
21331
|
+
keys.add(key);
|
|
21332
|
+
const { id, rev: rev2 } = identity(entry, "a module", where, problems2);
|
|
21333
|
+
const parsed = communityModuleCreateBody.safeParse({ title: entry.title });
|
|
21334
|
+
if (!parsed.success) problems2.push(`${where}: ${contractProblem(parsed.error)}`);
|
|
21335
|
+
const lessons = [];
|
|
21336
|
+
for (const slug of readNameList(entry.lessons, `${where} lessons`, "lesson", problems2)) {
|
|
21337
|
+
if (listedLessons.has(slug)) {
|
|
21338
|
+
problems2.push(`${courseWhere}: lesson ${slug} is listed twice`);
|
|
21339
|
+
continue;
|
|
21340
|
+
}
|
|
21341
|
+
listedLessons.add(slug);
|
|
21342
|
+
const lesson = readLessonFile(communityFolder, lessonsDir, courseSlug, slug, problems2);
|
|
21343
|
+
if (lesson) lessons.push(lesson);
|
|
21344
|
+
}
|
|
21345
|
+
if (!parsed.success) return;
|
|
21346
|
+
modules.push({
|
|
21347
|
+
key,
|
|
21348
|
+
...id === void 0 ? {} : { id },
|
|
21349
|
+
...rev2 === void 0 ? {} : { rev: rev2 },
|
|
21350
|
+
title: entry.title,
|
|
21351
|
+
lessons
|
|
21352
|
+
});
|
|
21353
|
+
});
|
|
21354
|
+
return modules;
|
|
21355
|
+
}
|
|
21356
|
+
function splitFrontmatter(text) {
|
|
21357
|
+
if (!text.startsWith("---\n")) return null;
|
|
21358
|
+
const close2 = text.indexOf("\n---\n", 3);
|
|
21359
|
+
if (close2 === -1) return text.endsWith("\n---") ? { front: text.slice(4, -3), body: "" } : null;
|
|
21360
|
+
const rest = text.slice(close2 + 5);
|
|
21361
|
+
return { front: text.slice(4, close2 + 1), body: rest.endsWith("\n") ? rest.slice(0, -1) : rest };
|
|
21362
|
+
}
|
|
21363
|
+
function readLessonFile(communityFolder, lessonsDir, courseSlug, slug, problems2) {
|
|
21364
|
+
const name = `${slug}${LESSON_FILE_EXT}`;
|
|
21365
|
+
const where = `${COURSES_DIR}/${courseSlug}/${LESSONS_DIR}/${name}`;
|
|
21366
|
+
const raw = readRealFileOrThrow(communityFolder, path24.join(lessonsDir, name));
|
|
21367
|
+
if (raw === null) {
|
|
21368
|
+
const hint = slug.endsWith(LESSON_FILE_EXT) ? ` (list a lesson by its file's name less ${LESSON_FILE_EXT})` : "";
|
|
21369
|
+
problems2.push(`${COURSES_DIR}/${courseSlug}/${COURSE_FILE}: lesson ${slug} has no file ${LESSONS_DIR}/${name}${hint}`);
|
|
21370
|
+
return null;
|
|
21371
|
+
}
|
|
21372
|
+
const parts = splitFrontmatter(toLf(raw));
|
|
21373
|
+
if (!parts) {
|
|
21374
|
+
problems2.push(`${where}: must start with a frontmatter block (a line --- , the lesson's fields, a line ---)`);
|
|
21375
|
+
return null;
|
|
21376
|
+
}
|
|
21377
|
+
let doc;
|
|
21378
|
+
try {
|
|
21379
|
+
doc = parts.front.trim() === "" ? {} : yaml12.load(parts.front);
|
|
21380
|
+
} catch (err) {
|
|
21381
|
+
problems2.push(`${where}: the frontmatter is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
21382
|
+
return null;
|
|
21383
|
+
}
|
|
21384
|
+
if (!isMapping(doc)) {
|
|
21385
|
+
problems2.push(`${where}: the frontmatter must be a YAML mapping`);
|
|
21386
|
+
return null;
|
|
21387
|
+
}
|
|
21388
|
+
unknownKeys(doc, LESSON_KEYS, where, problems2);
|
|
21389
|
+
const { id, rev: rev2 } = identity(doc, "a lesson", where, problems2);
|
|
21390
|
+
const room = optionalString(doc, "room", where, problems2);
|
|
21391
|
+
if (room !== void 0 && !isPathSafeId(room)) problems2.push(`${where}: room must be a room's key in ${COMMUNITY_FILE}`);
|
|
21392
|
+
const videoUrl = optionalString(doc, "video_url", where, problems2);
|
|
21393
|
+
const published = optionalBoolean(doc, "published", where, problems2);
|
|
21394
|
+
const requiresPaidPlan = optionalBoolean(doc, "requires_paid_plan", where, problems2);
|
|
21395
|
+
const links = doc.links ?? [];
|
|
21396
|
+
const parsed = lessonFileBody.safeParse({
|
|
21397
|
+
title: doc.title,
|
|
21398
|
+
...videoUrl === void 0 ? {} : { video_url: videoUrl },
|
|
21399
|
+
body: parts.body,
|
|
21400
|
+
links
|
|
21401
|
+
});
|
|
21402
|
+
if (!parsed.success) {
|
|
21403
|
+
problems2.push(`${where}: ${contractProblem(parsed.error)}`);
|
|
21404
|
+
return null;
|
|
21405
|
+
}
|
|
21406
|
+
return {
|
|
21407
|
+
slug,
|
|
21408
|
+
...id === void 0 ? {} : { id },
|
|
21409
|
+
...rev2 === void 0 ? {} : { rev: rev2 },
|
|
21410
|
+
title: doc.title,
|
|
21411
|
+
published,
|
|
21412
|
+
...room === void 0 ? {} : { room },
|
|
21413
|
+
...videoUrl === void 0 ? {} : { video_url: videoUrl },
|
|
21414
|
+
links: links.map((link) => ({ title: link.title, url: link.url })),
|
|
21415
|
+
requires_paid_plan: requiresPaidPlan,
|
|
21416
|
+
body: parts.body
|
|
21417
|
+
};
|
|
21418
|
+
}
|
|
21419
|
+
function folderEntries(dir) {
|
|
21420
|
+
try {
|
|
21421
|
+
return fs18.readdirSync(dir).filter((name) => !name.startsWith(".")).sort();
|
|
21422
|
+
} catch {
|
|
21423
|
+
return [];
|
|
21424
|
+
}
|
|
21425
|
+
}
|
|
21426
|
+
function lessonFrontmatter(bytes) {
|
|
21427
|
+
const parts = bytes ? splitFrontmatter(toLf(bytes.toString("utf-8"))) : null;
|
|
21428
|
+
if (!parts) return null;
|
|
21429
|
+
try {
|
|
21430
|
+
return yaml12.load(parts.front);
|
|
21431
|
+
} catch {
|
|
21432
|
+
return null;
|
|
21433
|
+
}
|
|
21434
|
+
}
|
|
21435
|
+
function renderCourseYaml(course) {
|
|
21436
|
+
const doc = {
|
|
21437
|
+
...course.id ? { id: course.id } : {},
|
|
21438
|
+
...course.rev ? { rev: course.rev } : {},
|
|
21439
|
+
title: course.title,
|
|
21440
|
+
description: course.description,
|
|
21441
|
+
published: course.published,
|
|
21442
|
+
modules: course.modules.map((module) => ({
|
|
21443
|
+
key: module.key,
|
|
21444
|
+
...module.id ? { id: module.id } : {},
|
|
21445
|
+
...module.rev ? { rev: module.rev } : {},
|
|
21446
|
+
title: module.title,
|
|
21447
|
+
lessons: module.lessons.map((lesson) => lesson.slug)
|
|
21448
|
+
})),
|
|
21449
|
+
...course.synced ? {
|
|
21450
|
+
synced: {
|
|
21451
|
+
...course.synced.modules ? { modules: course.synced.modules } : {},
|
|
21452
|
+
lessons: Object.fromEntries(
|
|
21453
|
+
Object.entries(course.synced.lessons).sort(([a], [b]) => syncedRank(course, a) - syncedRank(course, b) || a.localeCompare(b))
|
|
21454
|
+
)
|
|
21455
|
+
}
|
|
21456
|
+
} : {}
|
|
21457
|
+
};
|
|
21458
|
+
return yaml12.dump(doc, YAML_DUMP_OPTIONS);
|
|
21459
|
+
}
|
|
21460
|
+
function syncedRank(course, moduleId) {
|
|
21461
|
+
const at = course.synced?.modules?.indexOf(moduleId) ?? -1;
|
|
21462
|
+
return at === -1 ? Number.MAX_SAFE_INTEGER : at;
|
|
21463
|
+
}
|
|
21464
|
+
function renderLessonFile(lesson) {
|
|
21465
|
+
const front = {
|
|
21466
|
+
...lesson.id ? { id: lesson.id } : {},
|
|
21467
|
+
...lesson.rev ? { rev: lesson.rev } : {},
|
|
21468
|
+
title: lesson.title,
|
|
21469
|
+
published: lesson.published,
|
|
21470
|
+
...lesson.room ? { room: lesson.room } : {},
|
|
21471
|
+
...lesson.video_url ? { video_url: lesson.video_url } : {},
|
|
21472
|
+
...lesson.links.length > 0 ? { links: lesson.links.map((link) => ({ title: link.title, url: link.url })) } : {},
|
|
21473
|
+
requires_paid_plan: lesson.requires_paid_plan
|
|
21474
|
+
};
|
|
21475
|
+
return `---
|
|
21476
|
+
${yaml12.dump(front, YAML_DUMP_OPTIONS)}---
|
|
21477
|
+
${toLf(lesson.body)}
|
|
21478
|
+
`;
|
|
21479
|
+
}
|
|
21480
|
+
function writeCourseYaml(root, communityFolder, course) {
|
|
21481
|
+
return writeFileIfChangedNoFollow(root, path24.join(communityFolder, coursePath(course)), renderCourseYaml(course));
|
|
21482
|
+
}
|
|
21483
|
+
function writeLessonFile(root, communityFolder, course, lesson) {
|
|
21484
|
+
return writeFileIfChangedNoFollow(root, path24.join(communityFolder, lessonPath(course, lesson)), renderLessonFile(lesson));
|
|
21485
|
+
}
|
|
21486
|
+
function writeCourseFolder(root, communityFolder, course) {
|
|
21487
|
+
const written = writeCourseYaml(root, communityFolder, course) ? [coursePath(course)] : [];
|
|
21488
|
+
for (const module of course.modules) {
|
|
21489
|
+
for (const lesson of module.lessons) {
|
|
21490
|
+
if (writeLessonFile(root, communityFolder, course, lesson)) written.push(lessonPath(course, lesson));
|
|
21491
|
+
}
|
|
21492
|
+
}
|
|
21493
|
+
return written;
|
|
21494
|
+
}
|
|
21495
|
+
var COURSES_DIR, COURSE_FILE, LESSONS_DIR, LESSON_FILE_EXT, COURSE_KEYS, MODULE_KEYS, COURSE_SYNCED_KEYS, LESSON_KEYS, lessonFileBody, coursePath, lessonPath, unlistedPath;
|
|
21496
|
+
var init_course_files = __esm({
|
|
21497
|
+
"src/communities/course-files.ts"() {
|
|
21498
|
+
"use strict";
|
|
21499
|
+
init_contracts();
|
|
21500
|
+
init_base_id();
|
|
21501
|
+
init_community_workspace();
|
|
21502
|
+
init_fs_safety();
|
|
21503
|
+
init_layout();
|
|
21504
|
+
init_yaml_writer();
|
|
21505
|
+
init_utils();
|
|
21506
|
+
init_model();
|
|
21507
|
+
COURSES_DIR = "courses";
|
|
21508
|
+
COURSE_FILE = "course.yaml";
|
|
21509
|
+
LESSONS_DIR = "lessons";
|
|
21510
|
+
LESSON_FILE_EXT = ".md";
|
|
21511
|
+
COURSE_KEYS = ["id", "rev", "title", "description", "published", "modules", "synced"];
|
|
21512
|
+
MODULE_KEYS = ["key", "id", "rev", "title", "lessons"];
|
|
21513
|
+
COURSE_SYNCED_KEYS = ["modules", "lessons"];
|
|
21514
|
+
LESSON_KEYS = ["id", "rev", "title", "published", "room", "video_url", "links", "requires_paid_plan"];
|
|
21515
|
+
lessonFileBody = communityLessonCreateBody.omit({ module_id: true, room_id: true, client_id: true });
|
|
21516
|
+
coursePath = (course) => path24.join(COURSES_DIR, course.slug, COURSE_FILE);
|
|
21517
|
+
lessonPath = (course, lesson) => path24.join(COURSES_DIR, course.slug, LESSONS_DIR, `${lesson.slug}${LESSON_FILE_EXT}`);
|
|
21518
|
+
unlistedPath = (name, course) => course ? path24.join(COURSES_DIR, course.slug, LESSONS_DIR, name) : `${path24.join(COURSES_DIR, name)}/`;
|
|
21519
|
+
}
|
|
21520
|
+
});
|
|
21521
|
+
|
|
21522
|
+
// src/communities/files.ts
|
|
21523
|
+
import * as path25 from "path";
|
|
21524
|
+
import * as yaml13 from "js-yaml";
|
|
20940
21525
|
function readCommunityFolder(folder) {
|
|
20941
|
-
const where =
|
|
20942
|
-
const yamlPath =
|
|
21526
|
+
const where = path25.basename(folder);
|
|
21527
|
+
const yamlPath = path25.join(folder, COMMUNITY_FILE);
|
|
20943
21528
|
const text = readRealFileOrThrow(folder, yamlPath);
|
|
20944
21529
|
if (text === null) {
|
|
20945
21530
|
throw expected(`No ${COMMUNITY_FILE} in ${where}/: pull the community first (wayai pull communities/<community_id>).`);
|
|
20946
21531
|
}
|
|
20947
|
-
const rules = toLf(readRealFileOrThrow(folder,
|
|
21532
|
+
const rules = toLf(readRealFileOrThrow(folder, path25.join(folder, RULES_FILE)) ?? "");
|
|
20948
21533
|
let doc;
|
|
20949
21534
|
try {
|
|
20950
|
-
doc =
|
|
21535
|
+
doc = yaml13.load(text);
|
|
20951
21536
|
} catch (err) {
|
|
20952
21537
|
throw expected(`${where}/${COMMUNITY_FILE} is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
20953
21538
|
}
|
|
@@ -20974,6 +21559,9 @@ function readCommunityFolder(folder) {
|
|
|
20974
21559
|
if (typeof doc.join_link_enabled !== "boolean") problems2.push(`${COMMUNITY_FILE}: join_link_enabled must be true or false`);
|
|
20975
21560
|
const joinLinkRev2 = optionalString(doc, "join_link_rev", COMMUNITY_FILE, problems2);
|
|
20976
21561
|
const rooms = readRooms(doc.rooms, problems2);
|
|
21562
|
+
const synced2 = readCommunitySynced(doc.synced, problems2);
|
|
21563
|
+
const { courses, unlisted } = readCourseFolders(folder, readNameList(doc.courses, `${COMMUNITY_FILE} courses`, "course folder", problems2), problems2);
|
|
21564
|
+
problems2.push(...crossFileProblems(rooms, courses));
|
|
20977
21565
|
if (problems2.length > 0) {
|
|
20978
21566
|
throw expected(`${where}/ cannot be synced:
|
|
20979
21567
|
${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
@@ -20989,9 +21577,45 @@ ${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
20989
21577
|
rules,
|
|
20990
21578
|
join_link_enabled: doc.join_link_enabled,
|
|
20991
21579
|
...joinLinkRev2 === void 0 ? {} : { join_link_rev: joinLinkRev2 },
|
|
20992
|
-
rooms
|
|
21580
|
+
rooms,
|
|
21581
|
+
courses,
|
|
21582
|
+
...synced2 === void 0 ? {} : { synced_courses: synced2 },
|
|
21583
|
+
unlistedCourses: unlisted
|
|
20993
21584
|
};
|
|
20994
21585
|
}
|
|
21586
|
+
function readCommunitySynced(raw, problems2) {
|
|
21587
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
21588
|
+
if (!isMapping(raw)) {
|
|
21589
|
+
problems2.push(`${COMMUNITY_FILE}: synced must be a mapping`);
|
|
21590
|
+
return void 0;
|
|
21591
|
+
}
|
|
21592
|
+
unknownKeys(raw, ["courses"], `${COMMUNITY_FILE} synced`, problems2);
|
|
21593
|
+
return readIdList(raw.courses, `${COMMUNITY_FILE} synced.courses`, problems2);
|
|
21594
|
+
}
|
|
21595
|
+
function crossFileProblems(rooms, courses) {
|
|
21596
|
+
const problems2 = [];
|
|
21597
|
+
const seen = /* @__PURE__ */ new Map();
|
|
21598
|
+
const once = (id, where) => {
|
|
21599
|
+
if (id === void 0) return;
|
|
21600
|
+
const first = seen.get(id);
|
|
21601
|
+
if (first) problems2.push(`${where}: id ${id} is also ${first}'s \u2014 a copy keeps its original's id; remove the id (and its revs) to make a new one`);
|
|
21602
|
+
else seen.set(id, where);
|
|
21603
|
+
};
|
|
21604
|
+
const roomKeys = new Set(rooms.map((room) => room.key));
|
|
21605
|
+
for (const course of courses) {
|
|
21606
|
+
once(course.id, `course ${course.slug}`);
|
|
21607
|
+
for (const module of course.modules) {
|
|
21608
|
+
once(module.id, `module ${course.slug}/${module.key}`);
|
|
21609
|
+
for (const lesson of module.lessons) {
|
|
21610
|
+
once(lesson.id, `lesson ${course.slug}/${lesson.slug}`);
|
|
21611
|
+
if (lesson.room !== void 0 && !roomKeys.has(lesson.room)) {
|
|
21612
|
+
problems2.push(`lesson ${course.slug}/${lesson.slug}: room ${lesson.room} is not a room key in ${COMMUNITY_FILE}`);
|
|
21613
|
+
}
|
|
21614
|
+
}
|
|
21615
|
+
}
|
|
21616
|
+
}
|
|
21617
|
+
return problems2;
|
|
21618
|
+
}
|
|
20995
21619
|
function readRooms(raw, problems2) {
|
|
20996
21620
|
if (raw === void 0 || raw === null) return [];
|
|
20997
21621
|
if (!Array.isArray(raw)) {
|
|
@@ -21062,31 +21686,30 @@ function renderCommunityYaml(files) {
|
|
|
21062
21686
|
name: room.name,
|
|
21063
21687
|
kind: room.kind,
|
|
21064
21688
|
...room.archived ? { archived: true } : {}
|
|
21065
|
-
}))
|
|
21689
|
+
})),
|
|
21690
|
+
courses: files.courses.map((course) => course.slug),
|
|
21691
|
+
...files.synced_courses ? { synced: { courses: files.synced_courses } } : {}
|
|
21066
21692
|
};
|
|
21067
|
-
return
|
|
21693
|
+
return yaml13.dump(doc, YAML_DUMP_OPTIONS);
|
|
21068
21694
|
}
|
|
21069
21695
|
function renderRules(rules) {
|
|
21070
21696
|
const text = toLf(rules).replace(/\n+$/, "");
|
|
21071
21697
|
return text === "" ? "" : `${text}
|
|
21072
21698
|
`;
|
|
21073
21699
|
}
|
|
21074
|
-
function
|
|
21700
|
+
function writeCommunityFiles(root, folder, files) {
|
|
21075
21701
|
const written = [];
|
|
21076
21702
|
for (const [name, content] of [
|
|
21077
21703
|
[COMMUNITY_FILE, renderCommunityYaml(files)],
|
|
21078
21704
|
[RULES_FILE, renderRules(files.rules)]
|
|
21079
21705
|
]) {
|
|
21080
|
-
|
|
21081
|
-
const bytes = Buffer.from(content, "utf-8");
|
|
21082
|
-
if (fileHasBytes(abs, bytes)) continue;
|
|
21083
|
-
if (!writeFileNoFollow(root, abs, bytes)) {
|
|
21084
|
-
throw symlinkRefusal(path24.relative(path24.dirname(root), abs), "file");
|
|
21085
|
-
}
|
|
21086
|
-
written.push(name);
|
|
21706
|
+
if (writeFileIfChangedNoFollow(root, path25.join(folder, name), content)) written.push(name);
|
|
21087
21707
|
}
|
|
21088
21708
|
return written;
|
|
21089
21709
|
}
|
|
21710
|
+
function writeCommunityFolder(root, folder, files) {
|
|
21711
|
+
return [...writeCommunityFiles(root, folder, files), ...files.courses.flatMap((course) => writeCourseFolder(root, folder, course))];
|
|
21712
|
+
}
|
|
21090
21713
|
var TOP_KEYS, SETTINGS_KEYS, ROOM_KEYS;
|
|
21091
21714
|
var init_files = __esm({
|
|
21092
21715
|
"src/communities/files.ts"() {
|
|
@@ -21099,20 +21722,1365 @@ var init_files = __esm({
|
|
|
21099
21722
|
init_community_workspace();
|
|
21100
21723
|
init_utils();
|
|
21101
21724
|
init_model();
|
|
21102
|
-
|
|
21725
|
+
init_course_files();
|
|
21726
|
+
TOP_KEYS = ["community_id", "name", "settings", "join_link_enabled", "join_link_rev", "rooms", "courses", "synced"];
|
|
21103
21727
|
SETTINGS_KEYS = ["rev", "slow_mode_seconds", "courses_label"];
|
|
21104
21728
|
ROOM_KEYS = ["key", "id", "rev", "name", "kind", "archived"];
|
|
21105
21729
|
}
|
|
21106
21730
|
});
|
|
21107
21731
|
|
|
21732
|
+
// src/communities/listing.ts
|
|
21733
|
+
function lineDiff(from, to) {
|
|
21734
|
+
const a = from ? sanitizeTerminalText(from).split("\n") : [];
|
|
21735
|
+
const b = to ? sanitizeTerminalText(to).split("\n") : [];
|
|
21736
|
+
if (a.length * b.length > 25e4) return [...a.map((l) => `- ${l}`), ...b.map((l) => `+ ${l}`)];
|
|
21737
|
+
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
21738
|
+
for (let i2 = a.length - 1; i2 >= 0; i2--) {
|
|
21739
|
+
for (let j2 = b.length - 1; j2 >= 0; j2--) {
|
|
21740
|
+
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
21741
|
+
}
|
|
21742
|
+
}
|
|
21743
|
+
const out = [];
|
|
21744
|
+
let i = 0;
|
|
21745
|
+
let j = 0;
|
|
21746
|
+
while (i < a.length || j < b.length) {
|
|
21747
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
21748
|
+
i++;
|
|
21749
|
+
j++;
|
|
21750
|
+
} else if (j >= b.length || i < a.length && lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
21751
|
+
out.push(`- ${a[i++]}`);
|
|
21752
|
+
} else {
|
|
21753
|
+
out.push(`+ ${b[j++]}`);
|
|
21754
|
+
}
|
|
21755
|
+
}
|
|
21756
|
+
return out;
|
|
21757
|
+
}
|
|
21758
|
+
function printSection(title, lines) {
|
|
21759
|
+
if (lines.length === 0) return;
|
|
21760
|
+
console.log(`
|
|
21761
|
+
${title}`);
|
|
21762
|
+
for (const line of lines) console.log(` ${line}`);
|
|
21763
|
+
}
|
|
21764
|
+
function printListing(listing) {
|
|
21765
|
+
printSection("Changes:", listing.changes);
|
|
21766
|
+
printSection("Changed on the server since your last pull \u2014 left as is; pull to take it:", listing.serverChanged);
|
|
21767
|
+
printSection("Kept \u2014 edited in your files, not pushed yet:", listing.kept);
|
|
21768
|
+
printSection("Not in your files \u2014 left as is:", listing.leftAsIs);
|
|
21769
|
+
printSection("Conflicts \u2014 changed in your files and on the server since your last pull:", listing.conflicts);
|
|
21770
|
+
}
|
|
21771
|
+
var show, emptyListing;
|
|
21772
|
+
var init_listing = __esm({
|
|
21773
|
+
"src/communities/listing.ts"() {
|
|
21774
|
+
"use strict";
|
|
21775
|
+
init_terminal_output();
|
|
21776
|
+
show = (text) => JSON.stringify(sanitizeTerminalText(text));
|
|
21777
|
+
emptyListing = () => ({ changes: [], serverChanged: [], conflicts: [], leftAsIs: [], kept: [] });
|
|
21778
|
+
}
|
|
21779
|
+
});
|
|
21780
|
+
|
|
21781
|
+
// src/communities/order.ts
|
|
21782
|
+
function idsOf(entries) {
|
|
21783
|
+
return new Set(entries.flatMap((entry) => entry.id === void 0 ? [] : [entry.id]));
|
|
21784
|
+
}
|
|
21785
|
+
function restrict(order, ids) {
|
|
21786
|
+
const keep = new Set(ids);
|
|
21787
|
+
return order.filter((id) => keep.has(id));
|
|
21788
|
+
}
|
|
21789
|
+
function mergeOrder(listed2, server, named) {
|
|
21790
|
+
const listedIds2 = idsOf(listed2);
|
|
21791
|
+
const first = [];
|
|
21792
|
+
const after = /* @__PURE__ */ new Map();
|
|
21793
|
+
let anchor = null;
|
|
21794
|
+
for (const id of server) {
|
|
21795
|
+
if (listedIds2.has(id)) {
|
|
21796
|
+
anchor = id;
|
|
21797
|
+
} else if (!named.has(id)) {
|
|
21798
|
+
if (anchor === null) first.push(id);
|
|
21799
|
+
else after.set(anchor, [...after.get(anchor) ?? [], id]);
|
|
21800
|
+
}
|
|
21801
|
+
}
|
|
21802
|
+
const slots = first.map((id) => ({ untracked: id }));
|
|
21803
|
+
for (const child of listed2) {
|
|
21804
|
+
slots.push({ entry: child.entry });
|
|
21805
|
+
if (child.id !== void 0) for (const id of after.get(child.id) ?? []) slots.push({ untracked: id });
|
|
21806
|
+
}
|
|
21807
|
+
return slots;
|
|
21808
|
+
}
|
|
21809
|
+
function filesOrder(listed2, server, named) {
|
|
21810
|
+
const byEntry = new Map(listed2.map((child) => [child.entry, child.id]));
|
|
21811
|
+
return mergeOrder(listed2, server, named).flatMap((slot) => {
|
|
21812
|
+
const id = "untracked" in slot ? slot.untracked : byEntry.get(slot.entry);
|
|
21813
|
+
return id === void 0 ? [] : [id];
|
|
21814
|
+
});
|
|
21815
|
+
}
|
|
21816
|
+
function placeAt(list, id, position) {
|
|
21817
|
+
const at = list.indexOf(id);
|
|
21818
|
+
if (at >= 0) list.splice(at, 1);
|
|
21819
|
+
list.splice(Math.min(position, list.length), 0, id);
|
|
21820
|
+
}
|
|
21821
|
+
function sameOrder(a, b) {
|
|
21822
|
+
return a.length === b.length && a.every((id, i) => id === b[i]);
|
|
21823
|
+
}
|
|
21824
|
+
function reorderMoves(current, target) {
|
|
21825
|
+
const list = [...current];
|
|
21826
|
+
const present = target.filter((id) => list.includes(id));
|
|
21827
|
+
const stay = longestRising(present, present.map((id) => list.indexOf(id)));
|
|
21828
|
+
const moves = [];
|
|
21829
|
+
target.forEach((id, i) => {
|
|
21830
|
+
if (stay.has(id)) return;
|
|
21831
|
+
const others = list.filter((other) => other !== id);
|
|
21832
|
+
const position = i === 0 ? 0 : others.indexOf(target[i - 1]) + 1;
|
|
21833
|
+
moves.push({ id, position });
|
|
21834
|
+
placeAt(list, id, position);
|
|
21835
|
+
});
|
|
21836
|
+
const placed = list.filter((id) => target.includes(id));
|
|
21837
|
+
if (!sameOrder(placed, target)) {
|
|
21838
|
+
throw new Error(`reorderMoves: ${JSON.stringify(current)} \u2192 ${JSON.stringify(target)} left ${JSON.stringify(placed)}`);
|
|
21839
|
+
}
|
|
21840
|
+
return moves;
|
|
21841
|
+
}
|
|
21842
|
+
function longestRising(ids, positions) {
|
|
21843
|
+
const length = positions.map(() => 1);
|
|
21844
|
+
const previous = positions.map(() => -1);
|
|
21845
|
+
let best = -1;
|
|
21846
|
+
positions.forEach((position, i) => {
|
|
21847
|
+
for (let j = 0; j < i; j++) {
|
|
21848
|
+
if (positions[j] < position && length[j] + 1 > length[i]) {
|
|
21849
|
+
length[i] = length[j] + 1;
|
|
21850
|
+
previous[i] = j;
|
|
21851
|
+
}
|
|
21852
|
+
}
|
|
21853
|
+
if (best === -1 || length[i] > length[best]) best = i;
|
|
21854
|
+
});
|
|
21855
|
+
const stay = /* @__PURE__ */ new Set();
|
|
21856
|
+
for (let i = best; i >= 0; i = previous[i]) stay.add(ids[i]);
|
|
21857
|
+
return stay;
|
|
21858
|
+
}
|
|
21859
|
+
var init_order = __esm({
|
|
21860
|
+
"src/communities/order.ts"() {
|
|
21861
|
+
"use strict";
|
|
21862
|
+
}
|
|
21863
|
+
});
|
|
21864
|
+
|
|
21865
|
+
// src/communities/courses.ts
|
|
21866
|
+
import { isDeepStrictEqual } from "util";
|
|
21867
|
+
import * as path26 from "path";
|
|
21868
|
+
function roomIdOf(rooms, key) {
|
|
21869
|
+
if (key === void 0) return null;
|
|
21870
|
+
const room = rooms.find((entry) => entry.key === key);
|
|
21871
|
+
return room?.id ?? `${PENDING_ROOM}${key}`;
|
|
21872
|
+
}
|
|
21873
|
+
function fileLesson(rooms, lesson) {
|
|
21874
|
+
return lessonContent({
|
|
21875
|
+
title: lesson.title,
|
|
21876
|
+
body: lesson.body,
|
|
21877
|
+
video: lesson.video_url === void 0 ? null : parseCommunityLessonVideoUrl(lesson.video_url),
|
|
21878
|
+
links: lesson.links,
|
|
21879
|
+
room_id: roomIdOf(rooms, lesson.room),
|
|
21880
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
21881
|
+
published: lesson.published
|
|
21882
|
+
});
|
|
21883
|
+
}
|
|
21884
|
+
function roomName(rooms, id) {
|
|
21885
|
+
if (id === null) return "none";
|
|
21886
|
+
if (id.startsWith(PENDING_ROOM)) return id.slice(PENDING_ROOM.length);
|
|
21887
|
+
return rooms.find((room) => room.id === id)?.key ?? id;
|
|
21888
|
+
}
|
|
21889
|
+
function roomKeyOf(rooms, roomId) {
|
|
21890
|
+
if (roomId === null) return void 0;
|
|
21891
|
+
const room = rooms.find((entry) => entry.id === roomId);
|
|
21892
|
+
if (!room) throw expected(`room ${roomId} is not one of the rooms ${COMMUNITY_FILE} lists: pull the community first.`);
|
|
21893
|
+
return room.key;
|
|
21894
|
+
}
|
|
21895
|
+
function takeLesson(rooms, lesson, server, fields) {
|
|
21896
|
+
const file = fileLesson(rooms, lesson);
|
|
21897
|
+
const side = lessonContent(server);
|
|
21898
|
+
const take = (field, differs) => differs && (fields === void 0 || fields.has(field));
|
|
21899
|
+
if (take("title", file.title !== side.title)) lesson.title = server.title;
|
|
21900
|
+
if (take("body", file.body !== side.body)) lesson.body = side.body;
|
|
21901
|
+
if (take("video_url", !isDeepStrictEqual(file.video, side.video))) lesson.video_url = side.video ? communityLessonVideoWatchUrl(side.video) : void 0;
|
|
21902
|
+
if (take("links", !isDeepStrictEqual(file.links, side.links))) lesson.links = server.links.map((link) => ({ title: link.title, url: link.url }));
|
|
21903
|
+
if (take("room_id", file.room_id !== side.room_id)) lesson.room = roomKeyOf(rooms, side.room_id);
|
|
21904
|
+
if (take("requires_paid_plan", file.requires_paid_plan !== side.requires_paid_plan)) lesson.requires_paid_plan = side.requires_paid_plan;
|
|
21905
|
+
if (take("published", file.published !== side.published)) lesson.published = side.published;
|
|
21906
|
+
}
|
|
21907
|
+
function takeCourse(folder, server, fields) {
|
|
21908
|
+
const file = courseContent(folder);
|
|
21909
|
+
const side = courseContent(server);
|
|
21910
|
+
const take = (field, differs) => differs && (fields === void 0 || fields.has(field));
|
|
21911
|
+
if (take("title", file.title !== side.title)) folder.title = server.title;
|
|
21912
|
+
if (take("description", file.description !== side.description)) folder.description = server.description;
|
|
21913
|
+
if (take("published", file.published !== side.published)) folder.published = side.published;
|
|
21914
|
+
}
|
|
21915
|
+
function orderVerdict(list, base, server) {
|
|
21916
|
+
const onServer = new Set(server);
|
|
21917
|
+
if (base === void 0) {
|
|
21918
|
+
const shared2 = list.filter((id) => onServer.has(id));
|
|
21919
|
+
return sameOrder(shared2, restrict(server, shared2)) ? "converged" : "conflict";
|
|
21920
|
+
}
|
|
21921
|
+
const inBase = new Set(base);
|
|
21922
|
+
const shared = list.filter((id) => inBase.has(id) && onServer.has(id));
|
|
21923
|
+
const added = list.filter((id) => !inBase.has(id) && onServer.has(id));
|
|
21924
|
+
if (added.length > 0 && !sameGaps(added, shared, list, server)) return "conflict";
|
|
21925
|
+
const was = restrict(base, shared);
|
|
21926
|
+
const now = restrict(server, shared);
|
|
21927
|
+
return threeWay(!sameOrder(shared, was), !sameOrder(now, was), sameOrder(shared, now));
|
|
21928
|
+
}
|
|
21929
|
+
function sameGaps(added, shared, a, b) {
|
|
21930
|
+
const before = (order, id) => new Set(order.slice(0, order.indexOf(id)).filter((other) => shared.includes(other)));
|
|
21931
|
+
const same = (x, y) => x.size === y.size && [...x].every((id) => y.has(id));
|
|
21932
|
+
return sameOrder(restrict(a, added), restrict(b, added)) && added.every((id) => same(before(a, id), before(b, id)));
|
|
21933
|
+
}
|
|
21934
|
+
function placementVerdict(files, base, server) {
|
|
21935
|
+
if (base === void 0) return files === server ? "converged" : "conflict";
|
|
21936
|
+
return threeWay(files !== base, server !== base, files === server);
|
|
21937
|
+
}
|
|
21938
|
+
function planOrder(entries, server, base) {
|
|
21939
|
+
return { state: orderVerdict([...idsOf(entries)], base, server), server: [...server], base: base && [...base] };
|
|
21940
|
+
}
|
|
21941
|
+
function baseModule(synced2, lessonId) {
|
|
21942
|
+
const holding = Object.entries(synced2?.lessons ?? {}).filter(([, ids]) => ids.includes(lessonId));
|
|
21943
|
+
return holding.length === 1 ? holding[0][0] : void 0;
|
|
21944
|
+
}
|
|
21945
|
+
function courseName(files, server, courseId) {
|
|
21946
|
+
const folder = files.courses.find((course) => course.id === courseId);
|
|
21947
|
+
return folder ? `course ${folder.slug}` : `course ${show(server.course.get(courseId)?.course.title ?? courseId)}`;
|
|
21948
|
+
}
|
|
21949
|
+
function planCourses(files, server) {
|
|
21950
|
+
const problems2 = [];
|
|
21951
|
+
const named = idsOf(files.courses);
|
|
21952
|
+
return {
|
|
21953
|
+
server,
|
|
21954
|
+
order: planOrder(files.courses, server.ids, files.synced_courses),
|
|
21955
|
+
courses: files.courses.map((folder) => planCourse(files, folder, server, problems2)),
|
|
21956
|
+
untracked: server.courses.filter((course) => !named.has(course.course.course_id)),
|
|
21957
|
+
problems: problems2
|
|
21958
|
+
};
|
|
21959
|
+
}
|
|
21960
|
+
function planCourse(files, folder, server, problems2) {
|
|
21961
|
+
const found = folder.id === void 0 ? void 0 : server.course.get(folder.id);
|
|
21962
|
+
if (folder.id !== void 0 && !found) problems2.push(`course ${folder.slug}: ${folder.id} is not a course of this community`);
|
|
21963
|
+
const courseId = found?.course.course_id;
|
|
21964
|
+
const plan = {
|
|
21965
|
+
folder,
|
|
21966
|
+
server: null,
|
|
21967
|
+
export: found ?? null,
|
|
21968
|
+
modulesOrder: null,
|
|
21969
|
+
modules: [],
|
|
21970
|
+
lessons: [],
|
|
21971
|
+
untrackedModules: [],
|
|
21972
|
+
untrackedLessons: []
|
|
21973
|
+
};
|
|
21974
|
+
const moduleIds = idsOf(folder.modules);
|
|
21975
|
+
const lessonIds = idsOf(folder.modules.flatMap((module) => module.lessons));
|
|
21976
|
+
if (found) {
|
|
21977
|
+
const side = serverCourse(found.course);
|
|
21978
|
+
plan.server = { state: classify(folder.rev, courseRev(found.course.course_id, courseContent(folder)), side.rev), server: found.course, serverRev: side.rev };
|
|
21979
|
+
plan.modulesOrder = planOrder(folder.modules, found.modules.map((m) => m.module_id), folder.synced?.modules);
|
|
21980
|
+
plan.untrackedModules = found.modules.filter((module) => !moduleIds.has(module.module_id));
|
|
21981
|
+
plan.untrackedLessons = found.modules.flatMap((module) => module.lessons).filter((lesson) => !lessonIds.has(lesson.lesson_id));
|
|
21982
|
+
}
|
|
21983
|
+
const elsewhere = (what, at, id, owner) => {
|
|
21984
|
+
if (owner === void 0) problems2.push(`${what} ${at}: ${id} is not a ${what} of this community`);
|
|
21985
|
+
else problems2.push(`${what} ${at}: ${what} ${id} is one of ${courseName(files, server, owner)}'s on the server, and a ${what} does not move between courses`);
|
|
21986
|
+
};
|
|
21987
|
+
for (const entry of folder.modules) {
|
|
21988
|
+
const at = `${folder.slug}/${entry.key}`;
|
|
21989
|
+
const module = entry.id === void 0 ? void 0 : server.module.get(entry.id);
|
|
21990
|
+
if (entry.id !== void 0 && module?.courseId !== courseId) elsewhere("module", at, entry.id, module?.courseId);
|
|
21991
|
+
const mine = module && module.courseId === courseId ? module.module : void 0;
|
|
21992
|
+
const modulePlan = { course: folder, entry, server: null, lessonsOrder: null };
|
|
21993
|
+
if (mine) {
|
|
21994
|
+
const side = serverModule(mine);
|
|
21995
|
+
modulePlan.server = { state: classify(entry.rev, moduleRev(mine.module_id, moduleContent(entry)), side.rev), server: mine, serverRev: side.rev };
|
|
21996
|
+
modulePlan.lessonsOrder = planOrder(entry.lessons, mine.lessons.map((l) => l.lesson_id), folder.synced?.lessons[mine.module_id]);
|
|
21997
|
+
}
|
|
21998
|
+
plan.modules.push(modulePlan);
|
|
21999
|
+
for (const lesson of entry.lessons) {
|
|
22000
|
+
const where = server.lesson.get(lesson.id ?? "");
|
|
22001
|
+
if (lesson.id !== void 0 && where?.courseId !== courseId) elsewhere("lesson", `${folder.slug}/${lesson.slug}`, lesson.id, where?.courseId);
|
|
22002
|
+
const lessonPlan = { course: folder, module: entry, lesson, server: null, placement: null };
|
|
22003
|
+
if (where && where.courseId === courseId) {
|
|
22004
|
+
const side = serverLesson(where.lesson);
|
|
22005
|
+
lessonPlan.server = { state: classify(lesson.rev, lessonRev(where.lesson.lesson_id, fileLesson(files.rooms, lesson)), side.rev), server: where.lesson, serverRev: side.rev };
|
|
22006
|
+
const filesModule = plannedIds.module(folder, entry);
|
|
22007
|
+
const base = baseModule(folder.synced, where.lesson.lesson_id);
|
|
22008
|
+
lessonPlan.placement = { state: placementVerdict(filesModule, base, where.moduleId), base, files: filesModule, server: where.moduleId };
|
|
22009
|
+
}
|
|
22010
|
+
plan.lessons.push(lessonPlan);
|
|
22011
|
+
}
|
|
22012
|
+
}
|
|
22013
|
+
return plan;
|
|
22014
|
+
}
|
|
22015
|
+
function courseCapProblems(files, plan) {
|
|
22016
|
+
const problems2 = [];
|
|
22017
|
+
const title = (entity) => toLf(entity.title).trim();
|
|
22018
|
+
const courseCreates = plan.courses.filter((course) => course.folder.id === void 0).map((course) => title(course.folder));
|
|
22019
|
+
const courses = plan.server.courses.length;
|
|
22020
|
+
if (courses + certainlyNew(courseCreates, plan.untracked.map((course) => title(course.course))) > COMMUNITY_COURSES_MAX) {
|
|
22021
|
+
problems2.push(`courses: a community holds at most ${COMMUNITY_COURSES_MAX} courses, drafts included; it has ${courses}, and the files create ${courseCreates.length}`);
|
|
22022
|
+
}
|
|
22023
|
+
const named = idsOf(files.courses.flatMap((course) => course.modules.flatMap((module) => module.lessons)));
|
|
22024
|
+
const lessonCreates = [];
|
|
22025
|
+
for (const course of plan.courses) {
|
|
22026
|
+
const moduleCreates = course.modules.filter((module) => module.entry.id === void 0).map((module) => title(module.entry));
|
|
22027
|
+
const creates = course.lessons.filter((lesson) => lesson.lesson.id === void 0).map((lesson) => title(lesson.lesson));
|
|
22028
|
+
lessonCreates.push(...creates);
|
|
22029
|
+
const modules = course.export?.modules.length ?? 0;
|
|
22030
|
+
const lessons2 = course.export?.modules.reduce((n, module) => n + module.lessons.length, 0) ?? 0;
|
|
22031
|
+
if (modules + certainlyNew(moduleCreates, course.untrackedModules.map(title)) > COMMUNITY_COURSE_MODULES_MAX) {
|
|
22032
|
+
problems2.push(`course ${course.folder.slug}: a course holds at most ${COMMUNITY_COURSE_MODULES_MAX} modules; it has ${modules}, and the files create ${moduleCreates.length}`);
|
|
22033
|
+
}
|
|
22034
|
+
if (lessons2 + certainlyNew(creates, course.untrackedLessons.map(title)) > COMMUNITY_COURSE_LESSONS_MAX) {
|
|
22035
|
+
problems2.push(`course ${course.folder.slug}: a course holds at most ${COMMUNITY_COURSE_LESSONS_MAX} lessons, drafts included; it has ${lessons2}, and the files create ${creates.length}`);
|
|
22036
|
+
}
|
|
22037
|
+
}
|
|
22038
|
+
const lessons = plan.server.lesson.size;
|
|
22039
|
+
const untracked = [...plan.server.lesson.values()].filter(({ lesson }) => !named.has(lesson.lesson_id)).map(({ lesson }) => title(lesson));
|
|
22040
|
+
if (lessons + certainlyNew(lessonCreates, untracked) > COMMUNITY_LESSONS_MAX) {
|
|
22041
|
+
problems2.push(`lessons: a community holds at most ${COMMUNITY_LESSONS_MAX} lessons, drafts included; it has ${lessons}, and the files create ${lessonCreates.length}`);
|
|
22042
|
+
}
|
|
22043
|
+
return problems2;
|
|
22044
|
+
}
|
|
22045
|
+
function certainlyNew(creates, untracked) {
|
|
22046
|
+
const left = [...untracked];
|
|
22047
|
+
return creates.filter((title) => {
|
|
22048
|
+
const at = left.indexOf(title);
|
|
22049
|
+
if (at >= 0) left.splice(at, 1);
|
|
22050
|
+
return at < 0;
|
|
22051
|
+
}).length;
|
|
22052
|
+
}
|
|
22053
|
+
function append(order, id) {
|
|
22054
|
+
order.server.push(id);
|
|
22055
|
+
order.pinned?.push(id);
|
|
22056
|
+
order.touched = true;
|
|
22057
|
+
}
|
|
22058
|
+
function insertAfter(list, item, order) {
|
|
22059
|
+
let at = 0;
|
|
22060
|
+
for (let i = order.indexOf(item) - 1; i >= 0; i--) {
|
|
22061
|
+
const k = list.indexOf(order[i]);
|
|
22062
|
+
if (k >= 0) {
|
|
22063
|
+
at = k + 1;
|
|
22064
|
+
break;
|
|
22065
|
+
}
|
|
22066
|
+
}
|
|
22067
|
+
list.splice(at, 0, item);
|
|
22068
|
+
}
|
|
22069
|
+
function shift(order, id, position) {
|
|
22070
|
+
placeAt(order.server, id, position);
|
|
22071
|
+
order.touched = true;
|
|
22072
|
+
}
|
|
22073
|
+
function leave(order, id) {
|
|
22074
|
+
for (const list of [order.server, order.pinned ?? []]) {
|
|
22075
|
+
const at = list.indexOf(id);
|
|
22076
|
+
if (at >= 0) list.splice(at, 1);
|
|
22077
|
+
}
|
|
22078
|
+
order.touched = true;
|
|
22079
|
+
}
|
|
22080
|
+
function parents(files, sim, ids) {
|
|
22081
|
+
const out = [];
|
|
22082
|
+
const listedOf = (entries, id) => [...idsOf(listedIds(entries, id))];
|
|
22083
|
+
out.push({ what: "courses order", kind: "courses", id: files.community_id, order: sim.courses, courseId: null, listed: listedOf(files.courses, ids.course) });
|
|
22084
|
+
for (const course of files.courses) {
|
|
22085
|
+
const courseId = ids.course(course);
|
|
22086
|
+
const order = courseId === void 0 ? void 0 : sim.modules.get(courseId);
|
|
22087
|
+
if (courseId === void 0 || !order) continue;
|
|
22088
|
+
out.push({
|
|
22089
|
+
what: `modules order of course ${course.slug}`,
|
|
22090
|
+
kind: "modules",
|
|
22091
|
+
id: courseId,
|
|
22092
|
+
order,
|
|
22093
|
+
courseId,
|
|
22094
|
+
listed: listedOf(course.modules, (module) => ids.module(course, module))
|
|
22095
|
+
});
|
|
22096
|
+
}
|
|
22097
|
+
for (const course of files.courses) {
|
|
22098
|
+
const courseId = ids.course(course);
|
|
22099
|
+
if (courseId === void 0 || !sim.modules.has(courseId)) continue;
|
|
22100
|
+
for (const module of course.modules) {
|
|
22101
|
+
const moduleId = ids.module(course, module);
|
|
22102
|
+
const order = moduleId === void 0 ? void 0 : sim.lessons.get(moduleId);
|
|
22103
|
+
if (moduleId === void 0 || !order) continue;
|
|
22104
|
+
out.push({
|
|
22105
|
+
what: `lessons order of module ${course.slug}/${module.key}`,
|
|
22106
|
+
kind: "lessons",
|
|
22107
|
+
id: moduleId,
|
|
22108
|
+
order,
|
|
22109
|
+
courseId,
|
|
22110
|
+
listed: listedOf(module.lessons, (lesson) => ids.lesson(course, lesson))
|
|
22111
|
+
});
|
|
22112
|
+
}
|
|
22113
|
+
}
|
|
22114
|
+
return out;
|
|
22115
|
+
}
|
|
22116
|
+
function listedIds(entries, id) {
|
|
22117
|
+
return entries.map((entry) => ({ entry, id: id(entry) }));
|
|
22118
|
+
}
|
|
22119
|
+
function computePlaces(files, sim, ids, placements) {
|
|
22120
|
+
const out = { moves: [], conflicts: [], reorders: [] };
|
|
22121
|
+
const placementOf = (id) => placements.get(id);
|
|
22122
|
+
for (const parent of parents(files, sim, ids)) {
|
|
22123
|
+
const { order } = parent;
|
|
22124
|
+
if (order.conflict) continue;
|
|
22125
|
+
const lessons = parent.kind === "lessons";
|
|
22126
|
+
const wanted = lessons ? parent.listed.filter((id) => placementOf(id)?.state !== "remote") : parent.listed;
|
|
22127
|
+
const leaving = lessons ? order.server.filter((id) => placementOf(id)?.state === "local" && !parent.listed.includes(id)) : [];
|
|
22128
|
+
const arriving = wanted.filter((id) => !order.server.includes(id));
|
|
22129
|
+
if (order.pinned) {
|
|
22130
|
+
const kept = order.pinned;
|
|
22131
|
+
const shared = wanted.filter((id) => kept.includes(id));
|
|
22132
|
+
if (arriving.length > 0 || !sameOrder(shared, restrict(kept, shared))) out.conflicts.push(parent.what);
|
|
22133
|
+
continue;
|
|
22134
|
+
}
|
|
22135
|
+
if (arriving.length === 0 && sameOrder(restrict(order.server, wanted), wanted)) continue;
|
|
22136
|
+
const target = filesOrder(wanted.map((id) => ({ entry: id, id })), order.server, /* @__PURE__ */ new Set([...wanted, ...leaving]));
|
|
22137
|
+
const from = [...order.server];
|
|
22138
|
+
for (const move of reorderMoves(order.server, target)) {
|
|
22139
|
+
if (lessons) {
|
|
22140
|
+
const left = sim.at.get(move.id);
|
|
22141
|
+
if (left === void 0) throw new Error(`computePlaces: lesson ${move.id} is in no module`);
|
|
22142
|
+
if (left !== parent.id) leave(sim.lessons.get(left), move.id);
|
|
22143
|
+
sim.at.set(move.id, parent.id);
|
|
22144
|
+
}
|
|
22145
|
+
shift(order, move.id, move.position);
|
|
22146
|
+
const { id, position } = move;
|
|
22147
|
+
out.moves.push(
|
|
22148
|
+
parent.kind === "courses" ? { kind: "course", id, position } : parent.kind === "modules" ? { kind: "module", id, courseId: parent.id, position } : { kind: "lesson", id, courseId: parent.courseId, moduleId: parent.id, position }
|
|
22149
|
+
);
|
|
22150
|
+
}
|
|
22151
|
+
out.reorders.push({ what: parent.what, from, to: target });
|
|
22152
|
+
}
|
|
22153
|
+
return out;
|
|
22154
|
+
}
|
|
22155
|
+
function outcomeUnknown(err) {
|
|
22156
|
+
return err?.isExpected !== true;
|
|
22157
|
+
}
|
|
22158
|
+
function coursePatch(folder, now, publish) {
|
|
22159
|
+
const file = courseContent(folder);
|
|
22160
|
+
const server = courseContent(now);
|
|
22161
|
+
const body = {
|
|
22162
|
+
...file.title !== server.title ? { title: folder.title } : {},
|
|
22163
|
+
...file.description !== server.description ? { description: folder.description } : {},
|
|
22164
|
+
...file.published !== server.published && (publish || !file.published) ? { published: file.published } : {}
|
|
22165
|
+
};
|
|
22166
|
+
return Object.keys(body).length > 0 ? body : null;
|
|
22167
|
+
}
|
|
22168
|
+
function lessonPatch(rooms, lesson, now, waitForRooms) {
|
|
22169
|
+
const file = fileLesson(rooms, lesson);
|
|
22170
|
+
const server = lessonContent(now);
|
|
22171
|
+
const pending = file.room_id?.startsWith(PENDING_ROOM) ?? false;
|
|
22172
|
+
if (pending && !waitForRooms) throw new Error(`lessonPatch: room ${lesson.room} has no id yet`);
|
|
22173
|
+
const body = {
|
|
22174
|
+
...file.title !== server.title ? { title: lesson.title } : {},
|
|
22175
|
+
...file.body !== server.body ? { body: lesson.body } : {},
|
|
22176
|
+
...!isDeepStrictEqual(file.video, server.video) ? { video_url: lesson.video_url ?? null } : {},
|
|
22177
|
+
...!isDeepStrictEqual(file.links, server.links) ? { links: lesson.links } : {},
|
|
22178
|
+
...file.room_id !== server.room_id && !pending ? { room_id: file.room_id } : {},
|
|
22179
|
+
...file.requires_paid_plan !== server.requires_paid_plan ? { requires_paid_plan: lesson.requires_paid_plan } : {},
|
|
22180
|
+
...file.published !== server.published ? { published: lesson.published } : {}
|
|
22181
|
+
};
|
|
22182
|
+
return Object.keys(body).length > 0 ? body : null;
|
|
22183
|
+
}
|
|
22184
|
+
function courseChanges(from, to) {
|
|
22185
|
+
return [
|
|
22186
|
+
...from.title !== to.title ? [`title ${show(from.title)} \u2192 ${show(to.title)}`] : [],
|
|
22187
|
+
...from.description !== to.description ? ["description"] : [],
|
|
22188
|
+
...from.published !== to.published ? [to.published ? "published" : "unpublished"] : []
|
|
22189
|
+
];
|
|
22190
|
+
}
|
|
22191
|
+
function lessonChanges(from, to, room) {
|
|
22192
|
+
return [
|
|
22193
|
+
...from.title !== to.title ? [`title ${show(from.title)} \u2192 ${show(to.title)}`] : [],
|
|
22194
|
+
...from.body !== to.body ? ["text"] : [],
|
|
22195
|
+
...!isDeepStrictEqual(from.video, to.video) ? [to.video ? from.video ? "video" : "video added" : "video removed"] : [],
|
|
22196
|
+
...!isDeepStrictEqual(from.links, to.links) ? ["links"] : [],
|
|
22197
|
+
...from.room_id !== to.room_id ? [`room ${room(from.room_id)} \u2192 ${room(to.room_id)}`] : [],
|
|
22198
|
+
...from.requires_paid_plan !== to.requires_paid_plan ? [to.requires_paid_plan ? "needs a paid plan" : "no longer needs a paid plan"] : [],
|
|
22199
|
+
...from.published !== to.published ? [to.published ? "published" : "unpublished"] : []
|
|
22200
|
+
];
|
|
22201
|
+
}
|
|
22202
|
+
async function lessonWrite(course, lesson, write) {
|
|
22203
|
+
try {
|
|
22204
|
+
await write();
|
|
22205
|
+
} catch (err) {
|
|
22206
|
+
const reason = extractApiErrorReason(err);
|
|
22207
|
+
if (reason === null || !LESSON_TEXT_REASONS.includes(reason)) throw err;
|
|
22208
|
+
throw new Error(`${extractApiMessage(err)} \u2014 change the lesson's text in ${lessonPath(course, lesson)}`);
|
|
22209
|
+
}
|
|
22210
|
+
}
|
|
22211
|
+
function lessonFromServer(rooms, lesson, slug) {
|
|
22212
|
+
const room = roomKeyOf(rooms, lesson.room_id);
|
|
22213
|
+
return {
|
|
22214
|
+
slug,
|
|
22215
|
+
id: lesson.lesson_id,
|
|
22216
|
+
rev: serverLesson(lesson).rev,
|
|
22217
|
+
title: lesson.title,
|
|
22218
|
+
published: lesson.published,
|
|
22219
|
+
...room === void 0 ? {} : { room },
|
|
22220
|
+
...lesson.video ? { video_url: communityLessonVideoWatchUrl(lesson.video) } : {},
|
|
22221
|
+
links: lesson.links.map((link) => ({ title: link.title, url: link.url })),
|
|
22222
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
22223
|
+
body: toLf(lesson.body)
|
|
22224
|
+
};
|
|
22225
|
+
}
|
|
22226
|
+
function moduleFromServer(module, key) {
|
|
22227
|
+
return { key, id: module.module_id, rev: serverModule(module).rev, title: module.title, lessons: [] };
|
|
22228
|
+
}
|
|
22229
|
+
function syncedFromServer(course) {
|
|
22230
|
+
return {
|
|
22231
|
+
modules: course.modules.map((module) => module.module_id),
|
|
22232
|
+
lessons: Object.fromEntries(course.modules.map((module) => [module.module_id, module.lessons.map((lesson) => lesson.lesson_id)]))
|
|
22233
|
+
};
|
|
22234
|
+
}
|
|
22235
|
+
function courseFromServer(rooms, course, slug) {
|
|
22236
|
+
const keys = /* @__PURE__ */ new Set();
|
|
22237
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
22238
|
+
return {
|
|
22239
|
+
slug,
|
|
22240
|
+
id: course.course.course_id,
|
|
22241
|
+
rev: serverCourse(course.course).rev,
|
|
22242
|
+
title: course.course.title,
|
|
22243
|
+
description: course.course.description,
|
|
22244
|
+
published: course.course.published,
|
|
22245
|
+
modules: course.modules.map((module) => {
|
|
22246
|
+
const key = localName(module.title, module.module_id, keys, "module");
|
|
22247
|
+
keys.add(key);
|
|
22248
|
+
const entry = moduleFromServer(module, key);
|
|
22249
|
+
entry.lessons = module.lessons.map((lesson) => {
|
|
22250
|
+
const lessonSlug = localName(lesson.title, lesson.lesson_id, slugs, "lesson");
|
|
22251
|
+
slugs.add(lessonSlug);
|
|
22252
|
+
return lessonFromServer(rooms, lesson, lessonSlug);
|
|
22253
|
+
});
|
|
22254
|
+
return entry;
|
|
22255
|
+
}),
|
|
22256
|
+
synced: syncedFromServer(course),
|
|
22257
|
+
unlisted: []
|
|
22258
|
+
};
|
|
22259
|
+
}
|
|
22260
|
+
function coursesFromServer(files, server) {
|
|
22261
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
22262
|
+
files.courses = server.courses.map((course) => {
|
|
22263
|
+
const slug = localName(course.course.title, course.course.course_id, slugs, "course");
|
|
22264
|
+
slugs.add(slug);
|
|
22265
|
+
return courseFromServer(files.rooms, course, slug);
|
|
22266
|
+
});
|
|
22267
|
+
files.synced_courses = server.ids;
|
|
22268
|
+
}
|
|
22269
|
+
function takeOrder(entries, placed) {
|
|
22270
|
+
const out = [...placed];
|
|
22271
|
+
for (const entry of entries) if (!out.includes(entry)) insertAfter(out, entry, entries);
|
|
22272
|
+
return out;
|
|
22273
|
+
}
|
|
22274
|
+
function mergeArrivals(entries, server, arrivals) {
|
|
22275
|
+
const named = new Set(server.filter((id) => !arrivals.has(id)));
|
|
22276
|
+
return mergeOrder(listed(entries), server, named).flatMap((slot) => "entry" in slot ? [slot.entry] : arrivals.get(slot.untracked) ?? []);
|
|
22277
|
+
}
|
|
22278
|
+
function pullCourses(files, plan, takes, note, listing) {
|
|
22279
|
+
for (const course of plan.courses) pullContent(files, course, takes, note, listing);
|
|
22280
|
+
const tracked = new Map(files.courses.flatMap((course) => course.id === void 0 ? [] : [[course.id, course]]));
|
|
22281
|
+
const held = new Set(files.unlistedCourses.flatMap((folder) => folder.id === null ? [] : [folder.id]));
|
|
22282
|
+
const slugs = new Set([...files.courses.map((course) => course.slug), ...files.unlistedCourses.map((folder) => folder.name)].map((name) => name.toLowerCase()));
|
|
22283
|
+
const courseFor = (id) => {
|
|
22284
|
+
const known = tracked.get(id);
|
|
22285
|
+
if (known) return known;
|
|
22286
|
+
const course = plan.server.course.get(id);
|
|
22287
|
+
if (held.has(id)) {
|
|
22288
|
+
listing.kept.push(`course ${show(course.course.title)} (${id}): its folder is not listed in ${COMMUNITY_FILE} courses \u2014 list it to pull it`);
|
|
22289
|
+
return null;
|
|
22290
|
+
}
|
|
22291
|
+
const slug = localName(course.course.title, id, slugs, "course");
|
|
22292
|
+
slugs.add(slug);
|
|
22293
|
+
listing.changes.push(`+ course ${slug}: ${show(course.course.title)}`);
|
|
22294
|
+
return courseFromServer(files.rooms, course, slug);
|
|
22295
|
+
};
|
|
22296
|
+
const order = plan.order;
|
|
22297
|
+
if (takes(order.state)) {
|
|
22298
|
+
files.courses = takeOrder(files.courses, order.server.flatMap((id) => courseFor(id) ?? []));
|
|
22299
|
+
listing.changes.push("~ courses order");
|
|
22300
|
+
} else {
|
|
22301
|
+
const arrivals = new Map(order.server.filter((id) => !tracked.has(id)).flatMap((id) => {
|
|
22302
|
+
const course = courseFor(id);
|
|
22303
|
+
return course ? [[id, course]] : [];
|
|
22304
|
+
}));
|
|
22305
|
+
files.courses = mergeArrivals(files.courses, order.server, arrivals);
|
|
22306
|
+
note(order.state, "courses order");
|
|
22307
|
+
}
|
|
22308
|
+
if (synced(order.state, takes)) files.synced_courses = [...order.server];
|
|
22309
|
+
for (const course of plan.courses) if (course.export) pullTree(files, plan.server, course, takes, note, listing);
|
|
22310
|
+
}
|
|
22311
|
+
function pullContent(files, course, takes, note, listing) {
|
|
22312
|
+
const { folder } = course;
|
|
22313
|
+
if (course.server) {
|
|
22314
|
+
const { state, server, serverRev } = course.server;
|
|
22315
|
+
if (takes(state)) {
|
|
22316
|
+
listing.changes.push(`~ course ${folder.slug}: ${courseChanges(courseContent(folder), courseContent(server)).join("; ")}`);
|
|
22317
|
+
takeCourse(folder, server);
|
|
22318
|
+
}
|
|
22319
|
+
if (takes(state) || state === "converged") folder.rev = serverRev;
|
|
22320
|
+
note(state, `course ${folder.slug}`);
|
|
22321
|
+
} else if (folder.id === void 0) {
|
|
22322
|
+
listing.kept.push(`course ${folder.slug} (to create)`);
|
|
22323
|
+
}
|
|
22324
|
+
for (const module of course.modules) {
|
|
22325
|
+
const at = `module ${folder.slug}/${module.entry.key}`;
|
|
22326
|
+
if (!module.server) {
|
|
22327
|
+
if (module.entry.id === void 0) listing.kept.push(`${at} (to create)`);
|
|
22328
|
+
continue;
|
|
22329
|
+
}
|
|
22330
|
+
const { state, server, serverRev } = module.server;
|
|
22331
|
+
if (takes(state)) {
|
|
22332
|
+
listing.changes.push(`~ ${at}: title ${show(module.entry.title)} \u2192 ${show(server.title)}`);
|
|
22333
|
+
if (moduleContent(module.entry).title !== moduleContent(server).title) module.entry.title = server.title;
|
|
22334
|
+
}
|
|
22335
|
+
if (takes(state) || state === "converged") module.entry.rev = serverRev;
|
|
22336
|
+
note(state, at);
|
|
22337
|
+
}
|
|
22338
|
+
for (const lesson of course.lessons) {
|
|
22339
|
+
const at = `lesson ${folder.slug}/${lesson.lesson.slug}`;
|
|
22340
|
+
if (!lesson.server) {
|
|
22341
|
+
if (lesson.lesson.id === void 0) listing.kept.push(`${at} (to create)`);
|
|
22342
|
+
continue;
|
|
22343
|
+
}
|
|
22344
|
+
const { state, server, serverRev } = lesson.server;
|
|
22345
|
+
if (takes(state)) {
|
|
22346
|
+
const changes = lessonChanges(fileLesson(files.rooms, lesson.lesson), lessonContent(server), (id) => roomName(files.rooms, id));
|
|
22347
|
+
listing.changes.push(`~ ${at}: ${changes.join("; ")}`);
|
|
22348
|
+
takeLesson(files.rooms, lesson.lesson, server);
|
|
22349
|
+
}
|
|
22350
|
+
if (takes(state) || state === "converged") lesson.lesson.rev = serverRev;
|
|
22351
|
+
note(state, at);
|
|
22352
|
+
}
|
|
22353
|
+
}
|
|
22354
|
+
function pullTree(files, index, course, takes, note, listing) {
|
|
22355
|
+
const { folder } = course;
|
|
22356
|
+
const server = course.export;
|
|
22357
|
+
const keys = new Set(folder.modules.map((module) => module.key));
|
|
22358
|
+
const unlisted = folder.unlisted.map((file) => ({ slug: path26.basename(file.name, LESSON_FILE_EXT), id: file.id }));
|
|
22359
|
+
const slugs = new Set([...folder.modules.flatMap((module) => module.lessons.map((lesson) => lesson.slug)), ...unlisted.map((file) => file.slug)].map((name) => name.toLowerCase()));
|
|
22360
|
+
const held = new Set(unlisted.flatMap((file) => file.id === null ? [] : [file.id]));
|
|
22361
|
+
const fresh = /* @__PURE__ */ new Set();
|
|
22362
|
+
const moduleFor = (id) => {
|
|
22363
|
+
const known = folder.modules.find((module2) => module2.id === id);
|
|
22364
|
+
if (known) return known;
|
|
22365
|
+
const { module } = index.module.get(id);
|
|
22366
|
+
const key = localName(module.title, id, keys, "module");
|
|
22367
|
+
keys.add(key);
|
|
22368
|
+
listing.changes.push(`+ module ${folder.slug}/${key}: ${show(module.title)}`);
|
|
22369
|
+
const entry = moduleFromServer(module, key);
|
|
22370
|
+
fresh.add(entry);
|
|
22371
|
+
return entry;
|
|
22372
|
+
};
|
|
22373
|
+
const modulesOrder = course.modulesOrder;
|
|
22374
|
+
const what = `modules order of course ${folder.slug}`;
|
|
22375
|
+
if (takes(modulesOrder.state)) {
|
|
22376
|
+
folder.modules = takeOrder(folder.modules, modulesOrder.server.flatMap((id) => moduleFor(id) ?? []));
|
|
22377
|
+
listing.changes.push(`~ ${what}`);
|
|
22378
|
+
} else {
|
|
22379
|
+
const arrivals = new Map(modulesOrder.server.filter((id) => !folder.modules.some((m) => m.id === id)).flatMap((id) => {
|
|
22380
|
+
const entry = moduleFor(id);
|
|
22381
|
+
return entry ? [[id, entry]] : [];
|
|
22382
|
+
}));
|
|
22383
|
+
folder.modules = mergeArrivals(folder.modules, modulesOrder.server, arrivals);
|
|
22384
|
+
note(modulesOrder.state, what);
|
|
22385
|
+
}
|
|
22386
|
+
const entryOf = (moduleId) => folder.modules.find((module) => module.id === moduleId);
|
|
22387
|
+
const home = /* @__PURE__ */ new Map();
|
|
22388
|
+
const keptApart = /* @__PURE__ */ new Set();
|
|
22389
|
+
for (const lesson of course.lessons) {
|
|
22390
|
+
const { placement } = lesson;
|
|
22391
|
+
if (!placement) {
|
|
22392
|
+
home.set(lesson.lesson, lesson.module);
|
|
22393
|
+
continue;
|
|
22394
|
+
}
|
|
22395
|
+
const at = `lesson ${folder.slug}/${lesson.lesson.slug}`;
|
|
22396
|
+
if (placement.state === "conflict") note("conflict", `${at}: moved in the files and on the server`);
|
|
22397
|
+
if (placement.state === "local") listing.kept.push(`${at}: moved to module ${lesson.module.key}`);
|
|
22398
|
+
const taken = takes(placement.state) && placement.state !== "converged";
|
|
22399
|
+
const to = taken ? entryOf(placement.server) : void 0;
|
|
22400
|
+
if (to && to !== lesson.module) listing.changes.push(`~ ${at}: moved to module ${to.key}`);
|
|
22401
|
+
if (placement.state === "conflict" && !taken) keptApart.add(lesson);
|
|
22402
|
+
home.set(lesson.lesson, to ?? lesson.module);
|
|
22403
|
+
}
|
|
22404
|
+
const named = idsOf(course.lessons.map((lesson) => lesson.lesson));
|
|
22405
|
+
for (const module of server.modules) {
|
|
22406
|
+
const entry = entryOf(module.module_id);
|
|
22407
|
+
if (!entry) continue;
|
|
22408
|
+
for (const lesson of module.lessons) {
|
|
22409
|
+
if (named.has(lesson.lesson_id)) continue;
|
|
22410
|
+
if (held.has(lesson.lesson_id)) {
|
|
22411
|
+
listing.kept.push(`lesson ${show(lesson.title)} (${lesson.lesson_id}) of course ${folder.slug}: its file is listed by no module \u2014 list it to pull it`);
|
|
22412
|
+
continue;
|
|
22413
|
+
}
|
|
22414
|
+
const slug = localName(lesson.title, lesson.lesson_id, slugs, "lesson");
|
|
22415
|
+
slugs.add(slug);
|
|
22416
|
+
listing.changes.push(`+ lesson ${folder.slug}/${slug}: ${show(lesson.title)}`);
|
|
22417
|
+
home.set(lessonFromServer(files.rooms, lesson, slug), entry);
|
|
22418
|
+
}
|
|
22419
|
+
}
|
|
22420
|
+
for (const entry of folder.modules) {
|
|
22421
|
+
const members = [...home].filter(([, at]) => at === entry).map(([lesson]) => lesson);
|
|
22422
|
+
const own = entry.lessons.filter((lesson) => lesson.id === void 0 || members.includes(lesson));
|
|
22423
|
+
const serverIds = entry.id === void 0 ? [] : index.module.get(entry.id)?.module.lessons.map((lesson) => lesson.lesson_id) ?? [];
|
|
22424
|
+
const plan = course.modules.find((m) => m.entry === entry);
|
|
22425
|
+
const take = fresh.has(entry) || plan?.lessonsOrder !== null && plan?.lessonsOrder !== void 0 && takes(plan.lessonsOrder.state);
|
|
22426
|
+
if (take) {
|
|
22427
|
+
const byId = new Map(members.flatMap((lesson) => lesson.id === void 0 ? [] : [[lesson.id, lesson]]));
|
|
22428
|
+
entry.lessons = takeOrder(own, serverIds.flatMap((id) => byId.get(id) ?? []));
|
|
22429
|
+
if (!fresh.has(entry)) listing.changes.push(`~ lessons order of module ${folder.slug}/${entry.key}`);
|
|
22430
|
+
} else {
|
|
22431
|
+
const arrivals = new Map(members.filter((lesson) => !own.includes(lesson)).flatMap((lesson) => lesson.id === void 0 ? [] : [[lesson.id, lesson]]));
|
|
22432
|
+
entry.lessons = mergeArrivals(own, serverIds, arrivals);
|
|
22433
|
+
if (plan?.lessonsOrder) note(plan.lessonsOrder.state, `lessons order of module ${folder.slug}/${entry.key}`);
|
|
22434
|
+
}
|
|
22435
|
+
}
|
|
22436
|
+
const was = folder.synced;
|
|
22437
|
+
const lessons = {};
|
|
22438
|
+
for (const module of server.modules) {
|
|
22439
|
+
const verdict = course.modules.find((m) => m.entry.id === module.module_id)?.lessonsOrder;
|
|
22440
|
+
if (verdict && !synced(verdict.state, takes) && was?.lessons[module.module_id]) {
|
|
22441
|
+
lessons[module.module_id] = was.lessons[module.module_id];
|
|
22442
|
+
continue;
|
|
22443
|
+
}
|
|
22444
|
+
const kept = [...keptApart].filter((lesson) => lesson.placement.server === module.module_id).map((lesson) => lesson.lesson.id);
|
|
22445
|
+
const based = [...keptApart].filter((lesson) => lesson.placement.base === module.module_id).map((lesson) => lesson.lesson.id);
|
|
22446
|
+
const ids = module.lessons.map((lesson) => lesson.lesson_id).filter((id) => !kept.includes(id));
|
|
22447
|
+
for (const id of based) if (!ids.includes(id)) insertAfter(ids, id, was?.lessons[module.module_id] ?? []);
|
|
22448
|
+
lessons[module.module_id] = ids;
|
|
22449
|
+
}
|
|
22450
|
+
folder.synced = {
|
|
22451
|
+
modules: synced(modulesOrder.state, takes) || !was?.modules ? server.modules.map((module) => module.module_id) : was.modules,
|
|
22452
|
+
lessons
|
|
22453
|
+
};
|
|
22454
|
+
}
|
|
22455
|
+
function coursesChangedSince(before, after) {
|
|
22456
|
+
const states = (server) => {
|
|
22457
|
+
const out = /* @__PURE__ */ new Map();
|
|
22458
|
+
out.set("courses", { label: "courses order", rev: server.ids.join() });
|
|
22459
|
+
for (const course of server.courses) {
|
|
22460
|
+
const courseId = course.course.course_id;
|
|
22461
|
+
const label = `course ${show(course.course.title)}`;
|
|
22462
|
+
out.set(courseId, { label, rev: serverCourse(course.course).rev });
|
|
22463
|
+
out.set(`modules:${courseId}`, { label, rev: course.modules.map((m) => m.module_id).join() });
|
|
22464
|
+
for (const module of course.modules) {
|
|
22465
|
+
const moduleLabel = `module ${show(module.title)}`;
|
|
22466
|
+
out.set(module.module_id, { label: moduleLabel, rev: serverModule(module).rev });
|
|
22467
|
+
out.set(`lessons:${module.module_id}`, { label: moduleLabel, rev: module.lessons.map((l) => l.lesson_id).join() });
|
|
22468
|
+
for (const lesson of module.lessons) out.set(lesson.lesson_id, { label: `lesson ${show(lesson.title)}`, rev: serverLesson(lesson).rev });
|
|
22469
|
+
}
|
|
22470
|
+
}
|
|
22471
|
+
return out;
|
|
22472
|
+
};
|
|
22473
|
+
const was = states(before);
|
|
22474
|
+
const changed = /* @__PURE__ */ new Set();
|
|
22475
|
+
for (const [key, now] of states(after)) if (was.get(key)?.rev !== now.rev) changed.add(now.label);
|
|
22476
|
+
return [...changed];
|
|
22477
|
+
}
|
|
22478
|
+
var ServerCourses, PENDING_ROOM, listed, Sim, freshOrder, realIds, plannedIds, LESSON_TEXT_REASONS, CoursePush, synced;
|
|
22479
|
+
var init_courses = __esm({
|
|
22480
|
+
"src/communities/courses.ts"() {
|
|
22481
|
+
"use strict";
|
|
22482
|
+
init_contracts();
|
|
22483
|
+
init_community_workspace();
|
|
22484
|
+
init_errors2();
|
|
22485
|
+
init_expected();
|
|
22486
|
+
init_course_files();
|
|
22487
|
+
init_listing();
|
|
22488
|
+
init_model();
|
|
22489
|
+
init_order();
|
|
22490
|
+
ServerCourses = class {
|
|
22491
|
+
constructor(courses) {
|
|
22492
|
+
this.courses = courses;
|
|
22493
|
+
for (const course of courses) {
|
|
22494
|
+
const courseId = course.course.course_id;
|
|
22495
|
+
this.course.set(courseId, course);
|
|
22496
|
+
for (const module of course.modules) {
|
|
22497
|
+
this.module.set(module.module_id, { courseId, module });
|
|
22498
|
+
for (const lesson of module.lessons) this.lesson.set(lesson.lesson_id, { courseId, moduleId: module.module_id, lesson });
|
|
22499
|
+
}
|
|
22500
|
+
}
|
|
22501
|
+
}
|
|
22502
|
+
course = /* @__PURE__ */ new Map();
|
|
22503
|
+
module = /* @__PURE__ */ new Map();
|
|
22504
|
+
lesson = /* @__PURE__ */ new Map();
|
|
22505
|
+
get ids() {
|
|
22506
|
+
return this.courses.map((course) => course.course.course_id);
|
|
22507
|
+
}
|
|
22508
|
+
};
|
|
22509
|
+
PENDING_ROOM = "pending:";
|
|
22510
|
+
listed = (entries) => listedIds(entries, (entry) => entry.id);
|
|
22511
|
+
Sim = class _Sim {
|
|
22512
|
+
constructor(courses, modules = /* @__PURE__ */ new Map(), lessons = /* @__PURE__ */ new Map(), at = /* @__PURE__ */ new Map()) {
|
|
22513
|
+
this.courses = courses;
|
|
22514
|
+
this.modules = modules;
|
|
22515
|
+
this.lessons = lessons;
|
|
22516
|
+
this.at = at;
|
|
22517
|
+
}
|
|
22518
|
+
static from(plan) {
|
|
22519
|
+
const order = (ids, verdict) => ({
|
|
22520
|
+
server: [...ids],
|
|
22521
|
+
pinned: verdict?.state === "remote" ? [...verdict.base ?? []] : null,
|
|
22522
|
+
conflict: verdict?.state === "conflict",
|
|
22523
|
+
touched: false
|
|
22524
|
+
});
|
|
22525
|
+
const sim = new _Sim(order(plan.order.server, plan.order));
|
|
22526
|
+
for (const course of plan.server.courses) {
|
|
22527
|
+
const courseId = course.course.course_id;
|
|
22528
|
+
const tracked = plan.courses.find((c) => c.export?.course.course_id === courseId);
|
|
22529
|
+
sim.modules.set(courseId, order(course.modules.map((m) => m.module_id), tracked?.modulesOrder));
|
|
22530
|
+
for (const module of course.modules) {
|
|
22531
|
+
const entry = tracked?.modules.find((m) => m.server?.server.module_id === module.module_id);
|
|
22532
|
+
sim.lessons.set(module.module_id, order(module.lessons.map((l) => l.lesson_id), entry?.lessonsOrder));
|
|
22533
|
+
for (const lesson of module.lessons) sim.at.set(lesson.lesson_id, module.module_id);
|
|
22534
|
+
}
|
|
22535
|
+
}
|
|
22536
|
+
return sim;
|
|
22537
|
+
}
|
|
22538
|
+
clone() {
|
|
22539
|
+
const copy = (order) => ({ ...order, server: [...order.server], pinned: order.pinned && [...order.pinned] });
|
|
22540
|
+
const map = (orders) => new Map([...orders].map(([id, order]) => [id, copy(order)]));
|
|
22541
|
+
return new _Sim(copy(this.courses), map(this.modules), map(this.lessons), new Map(this.at));
|
|
22542
|
+
}
|
|
22543
|
+
};
|
|
22544
|
+
freshOrder = () => ({ server: [], pinned: null, conflict: false, touched: true });
|
|
22545
|
+
realIds = { course: (c) => c.id, module: (_c, m) => m.id, lesson: (_c, l) => l.id };
|
|
22546
|
+
plannedIds = {
|
|
22547
|
+
course: (c) => c.id ?? `new:course:${c.slug}`,
|
|
22548
|
+
module: (c, m) => m.id ?? `new:module:${c.slug}/${m.key}`,
|
|
22549
|
+
lesson: (c, l) => l.id ?? `new:lesson:${c.slug}/${l.slug}`
|
|
22550
|
+
};
|
|
22551
|
+
LESSON_TEXT_REASONS = [
|
|
22552
|
+
COMMUNITY_LESSON_LINE_PREFIX_TOO_WIDE_REASON,
|
|
22553
|
+
COMMUNITY_LESSON_TREE_TOO_DEEP_REASON,
|
|
22554
|
+
COMMUNITY_LESSON_TREE_TOO_LARGE_REASON
|
|
22555
|
+
];
|
|
22556
|
+
CoursePush = class {
|
|
22557
|
+
constructor(files, plan, remote, save) {
|
|
22558
|
+
this.files = files;
|
|
22559
|
+
this.plan = plan;
|
|
22560
|
+
this.remote = remote;
|
|
22561
|
+
this.save = save;
|
|
22562
|
+
this.sim = Sim.from(plan);
|
|
22563
|
+
for (const lesson of plan.courses.flatMap((course) => course.lessons)) {
|
|
22564
|
+
const { placement } = lesson;
|
|
22565
|
+
if (!placement || lesson.lesson.id === void 0) continue;
|
|
22566
|
+
this.placements.set(lesson.lesson.id, placement);
|
|
22567
|
+
if (placement.state !== "remote") continue;
|
|
22568
|
+
this.movedIn.set(placement.server, /* @__PURE__ */ new Set([...this.movedIn.get(placement.server) ?? [], lesson.lesson.id]));
|
|
22569
|
+
if (placement.base !== void 0) this.movedOut.set(placement.base, [...this.movedOut.get(placement.base) ?? [], lesson.lesson.id]);
|
|
22570
|
+
}
|
|
22571
|
+
for (const course of plan.server.courses) {
|
|
22572
|
+
this.courseNow.set(course.course.course_id, course.course);
|
|
22573
|
+
this.names.set(course.course.course_id, show(course.course.title));
|
|
22574
|
+
for (const module of course.modules) {
|
|
22575
|
+
this.moduleNow.set(module.module_id, module);
|
|
22576
|
+
this.names.set(module.module_id, show(module.title));
|
|
22577
|
+
for (const lesson of module.lessons) {
|
|
22578
|
+
this.lessonNow.set(lesson.lesson_id, lesson);
|
|
22579
|
+
this.names.set(lesson.lesson_id, show(lesson.title));
|
|
22580
|
+
}
|
|
22581
|
+
}
|
|
22582
|
+
}
|
|
22583
|
+
for (const course of files.courses) {
|
|
22584
|
+
this.names.set(plannedIds.course(course), course.slug);
|
|
22585
|
+
for (const module of course.modules) {
|
|
22586
|
+
this.names.set(plannedIds.module(course, module), module.key);
|
|
22587
|
+
for (const lesson of module.lessons) this.names.set(plannedIds.lesson(course, lesson), lesson.slug);
|
|
22588
|
+
}
|
|
22589
|
+
}
|
|
22590
|
+
this.predicted = this.predict();
|
|
22591
|
+
}
|
|
22592
|
+
sim;
|
|
22593
|
+
courseNow = /* @__PURE__ */ new Map();
|
|
22594
|
+
moduleNow = /* @__PURE__ */ new Map();
|
|
22595
|
+
lessonNow = /* @__PURE__ */ new Map();
|
|
22596
|
+
names = /* @__PURE__ */ new Map();
|
|
22597
|
+
/** Files whose synced lists changed since they were last saved. */
|
|
22598
|
+
unsaved = /* @__PURE__ */ new Set();
|
|
22599
|
+
/** Each listed lesson's place's verdict, by id. */
|
|
22600
|
+
placements = /* @__PURE__ */ new Map();
|
|
22601
|
+
/** The lessons the server moved since, which push leaves where they are: by module, those it moved in and out. */
|
|
22602
|
+
movedIn = /* @__PURE__ */ new Map();
|
|
22603
|
+
movedOut = /* @__PURE__ */ new Map();
|
|
22604
|
+
/** The places phase as the plan predicts it, every create landing as a new entity. */
|
|
22605
|
+
predicted;
|
|
22606
|
+
get communityId() {
|
|
22607
|
+
return this.files.community_id;
|
|
22608
|
+
}
|
|
22609
|
+
lessons() {
|
|
22610
|
+
return this.plan.courses.flatMap((course) => course.lessons);
|
|
22611
|
+
}
|
|
22612
|
+
/** The places phase on a copy of the server's orders where every create has landed, last. */
|
|
22613
|
+
predict() {
|
|
22614
|
+
const sim = this.sim.clone();
|
|
22615
|
+
for (const course of this.files.courses) {
|
|
22616
|
+
const courseId = plannedIds.course(course);
|
|
22617
|
+
if (course.id === void 0) {
|
|
22618
|
+
append(sim.courses, courseId);
|
|
22619
|
+
sim.modules.set(courseId, freshOrder());
|
|
22620
|
+
}
|
|
22621
|
+
for (const module of course.modules) {
|
|
22622
|
+
const moduleId = plannedIds.module(course, module);
|
|
22623
|
+
if (module.id === void 0) {
|
|
22624
|
+
const modules = sim.modules.get(courseId);
|
|
22625
|
+
if (!modules) continue;
|
|
22626
|
+
append(modules, moduleId);
|
|
22627
|
+
sim.lessons.set(moduleId, freshOrder());
|
|
22628
|
+
}
|
|
22629
|
+
for (const lesson of module.lessons) {
|
|
22630
|
+
const lessons = sim.lessons.get(moduleId);
|
|
22631
|
+
if (lesson.id !== void 0 || !lessons) continue;
|
|
22632
|
+
const lessonId = plannedIds.lesson(course, lesson);
|
|
22633
|
+
append(lessons, lessonId);
|
|
22634
|
+
sim.at.set(lessonId, moduleId);
|
|
22635
|
+
}
|
|
22636
|
+
}
|
|
22637
|
+
}
|
|
22638
|
+
return computePlaces(this.files, sim, plannedIds, this.placements);
|
|
22639
|
+
}
|
|
22640
|
+
// ── The listing ──
|
|
22641
|
+
roomName = (id) => roomName(this.files.rooms, id);
|
|
22642
|
+
/** Push's listing of the courses, server → files. */
|
|
22643
|
+
list(listing) {
|
|
22644
|
+
const { plan, files } = this;
|
|
22645
|
+
for (const course of plan.courses) {
|
|
22646
|
+
const { folder } = course;
|
|
22647
|
+
if (!course.server) {
|
|
22648
|
+
listing.changes.push(`+ course ${folder.slug}: create ${show(folder.title)}, ${folder.published ? "published once its lessons are in place" : "a draft"}`);
|
|
22649
|
+
} else if (course.server.state === "local") {
|
|
22650
|
+
listing.changes.push(`~ course ${folder.slug}: ${courseChanges(courseContent(course.server.server), courseContent(folder)).join("; ")}`);
|
|
22651
|
+
}
|
|
22652
|
+
for (const module of course.modules) {
|
|
22653
|
+
const at = `${folder.slug}/${module.entry.key}`;
|
|
22654
|
+
if (!module.server) listing.changes.push(`+ module ${at}: create ${show(module.entry.title)}`);
|
|
22655
|
+
else if (module.server.state === "local") listing.changes.push(`~ module ${at}: title ${show(module.server.server.title)} \u2192 ${show(module.entry.title)}`);
|
|
22656
|
+
}
|
|
22657
|
+
for (const lesson of course.lessons) {
|
|
22658
|
+
const at = `${folder.slug}/${lesson.lesson.slug}`;
|
|
22659
|
+
const file = fileLesson(files.rooms, lesson.lesson);
|
|
22660
|
+
if (!lesson.server) {
|
|
22661
|
+
const flags = [...file.published ? ["published"] : [], ...file.room_id ? [`room ${this.roomName(file.room_id)}`] : []];
|
|
22662
|
+
listing.changes.push(`+ lesson ${at}: create ${show(lesson.lesson.title)} in module ${lesson.module.key}${flags.length > 0 ? `, ${flags.join(", ")}` : ""}`);
|
|
22663
|
+
} else if (lesson.server.state === "local") {
|
|
22664
|
+
const server = lessonContent(lesson.server.server);
|
|
22665
|
+
listing.changes.push(`~ lesson ${at}: ${lessonChanges(server, file, this.roomName).join("; ")}`);
|
|
22666
|
+
if (server.body !== file.body) listing.changes.push(...lineDiff(server.body, file.body).map((line) => ` ${line}`));
|
|
22667
|
+
}
|
|
22668
|
+
}
|
|
22669
|
+
}
|
|
22670
|
+
for (const reorder of this.predicted.reorders) {
|
|
22671
|
+
listing.changes.push(`~ ${reorder.what}: ${reorder.from.map(this.name).join(", ")} \u2192 ${reorder.to.map(this.name).join(", ")}`);
|
|
22672
|
+
}
|
|
22673
|
+
const verdicts = (state) => [
|
|
22674
|
+
...plan.order.state === state ? ["courses order"] : [],
|
|
22675
|
+
...plan.courses.flatMap((course) => [
|
|
22676
|
+
...course.server?.state === state ? [`course ${course.folder.slug}`] : [],
|
|
22677
|
+
...course.modulesOrder?.state === state ? [`modules order of course ${course.folder.slug}`] : [],
|
|
22678
|
+
...course.modules.flatMap((module) => [
|
|
22679
|
+
...module.server?.state === state ? [`module ${course.folder.slug}/${module.entry.key}`] : [],
|
|
22680
|
+
...module.lessonsOrder?.state === state ? [`lessons order of module ${course.folder.slug}/${module.entry.key}`] : []
|
|
22681
|
+
]),
|
|
22682
|
+
...course.lessons.flatMap((lesson) => [
|
|
22683
|
+
...lesson.server?.state === state ? [`lesson ${course.folder.slug}/${lesson.lesson.slug}`] : [],
|
|
22684
|
+
...lesson.placement?.state === state ? [`lesson ${course.folder.slug}/${lesson.lesson.slug}: ${state === "conflict" ? "moved in the files and on the server" : `moved to module ${this.pathOf(lesson.placement.server)}`}`] : []
|
|
22685
|
+
])
|
|
22686
|
+
])
|
|
22687
|
+
];
|
|
22688
|
+
listing.serverChanged.push(...verdicts("remote"));
|
|
22689
|
+
listing.conflicts.push(.../* @__PURE__ */ new Set([...verdicts("conflict"), ...this.predicted.conflicts]));
|
|
22690
|
+
listing.leftAsIs.push(
|
|
22691
|
+
...plan.untracked.map((course) => `course ${show(course.course.title)} (${course.course.course_id})`),
|
|
22692
|
+
...plan.courses.flatMap((course) => [
|
|
22693
|
+
...course.untrackedModules.map((module) => `module ${show(module.title)} (${module.module_id}) of course ${course.folder.slug}`),
|
|
22694
|
+
...course.untrackedLessons.map((lesson) => `lesson ${show(lesson.title)} (${lesson.lesson_id}) of course ${course.folder.slug}`)
|
|
22695
|
+
])
|
|
22696
|
+
);
|
|
22697
|
+
}
|
|
22698
|
+
name = (id) => this.names.get(id) ?? id;
|
|
22699
|
+
/** Revs to refresh without a write: both sides agree, or the files carried no base. Saves the files they live in. */
|
|
22700
|
+
refreshConverged() {
|
|
22701
|
+
const { plan, files } = this;
|
|
22702
|
+
let refreshed = 0;
|
|
22703
|
+
const refresh = (converged, owner, set) => {
|
|
22704
|
+
if (!converged) return;
|
|
22705
|
+
set();
|
|
22706
|
+
this.unsaved.add(owner);
|
|
22707
|
+
refreshed++;
|
|
22708
|
+
};
|
|
22709
|
+
const rebase = (converged, order) => {
|
|
22710
|
+
if (!converged || !order) return;
|
|
22711
|
+
order.touched = true;
|
|
22712
|
+
refreshed++;
|
|
22713
|
+
};
|
|
22714
|
+
rebase(plan.order.state === "converged", this.sim.courses);
|
|
22715
|
+
for (const course of plan.courses) {
|
|
22716
|
+
const { folder } = course;
|
|
22717
|
+
refresh(course.server?.state === "converged", folder, () => {
|
|
22718
|
+
folder.rev = course.server.serverRev;
|
|
22719
|
+
});
|
|
22720
|
+
if (folder.id !== void 0) rebase(course.modulesOrder?.state === "converged", this.sim.modules.get(folder.id));
|
|
22721
|
+
for (const module of course.modules) {
|
|
22722
|
+
refresh(module.server?.state === "converged", folder, () => {
|
|
22723
|
+
module.entry.rev = module.server.serverRev;
|
|
22724
|
+
});
|
|
22725
|
+
if (module.entry.id !== void 0) rebase(module.lessonsOrder?.state === "converged", this.sim.lessons.get(module.entry.id));
|
|
22726
|
+
}
|
|
22727
|
+
for (const lesson of course.lessons) {
|
|
22728
|
+
const placement = lesson.placement;
|
|
22729
|
+
if (placement?.state !== "converged") continue;
|
|
22730
|
+
const was = placement.base === void 0 ? void 0 : this.sim.lessons.get(placement.base);
|
|
22731
|
+
if (was) {
|
|
22732
|
+
const at = was.pinned?.indexOf(lesson.lesson.id) ?? -1;
|
|
22733
|
+
if (at >= 0) was.pinned.splice(at, 1);
|
|
22734
|
+
was.touched = true;
|
|
22735
|
+
}
|
|
22736
|
+
const now = this.sim.lessons.get(placement.server);
|
|
22737
|
+
if (now) {
|
|
22738
|
+
if (now.pinned && !now.pinned.includes(lesson.lesson.id)) insertAfter(now.pinned, lesson.lesson.id, lesson.module.lessons.map((l) => l.id ?? ""));
|
|
22739
|
+
now.touched = true;
|
|
22740
|
+
}
|
|
22741
|
+
refreshed++;
|
|
22742
|
+
}
|
|
22743
|
+
for (const lesson of course.lessons) {
|
|
22744
|
+
if (lesson.server?.state === "converged") {
|
|
22745
|
+
lesson.lesson.rev = lesson.server.serverRev;
|
|
22746
|
+
this.save.lesson(folder, lesson.lesson);
|
|
22747
|
+
refreshed++;
|
|
22748
|
+
}
|
|
22749
|
+
}
|
|
22750
|
+
}
|
|
22751
|
+
this.settle();
|
|
22752
|
+
return refreshed;
|
|
22753
|
+
}
|
|
22754
|
+
/** Save the files whose synced lists changed. */
|
|
22755
|
+
flush() {
|
|
22756
|
+
for (const owner of this.unsaved) {
|
|
22757
|
+
if (owner === null) this.save.community();
|
|
22758
|
+
else this.save.course(owner);
|
|
22759
|
+
}
|
|
22760
|
+
this.unsaved.clear();
|
|
22761
|
+
}
|
|
22762
|
+
// ── The writes, phase by phase ──
|
|
22763
|
+
/** Courses, then lessons, that the files take off the community: first, before anything else changes. */
|
|
22764
|
+
unpublishWrites() {
|
|
22765
|
+
const writes = [];
|
|
22766
|
+
for (const course of this.plan.courses) {
|
|
22767
|
+
const { server, folder } = course;
|
|
22768
|
+
if (server?.state === "local" && server.server.published && !folder.published) {
|
|
22769
|
+
writes.push({ label: `course ${folder.slug} (unpublish)`, requests: 1, apply: () => this.updateCourse(folder, false) });
|
|
22770
|
+
}
|
|
22771
|
+
}
|
|
22772
|
+
for (const lesson of this.lessons()) {
|
|
22773
|
+
const { server, lesson: file, course } = lesson;
|
|
22774
|
+
if (server?.state === "local" && server.server.published && !file.published) {
|
|
22775
|
+
writes.push({ label: `lesson ${course.slug}/${file.slug} (unpublish)`, requests: 1, apply: () => lessonWrite(course, file, () => this.updateLesson(course, file, true)) });
|
|
22776
|
+
}
|
|
22777
|
+
}
|
|
22778
|
+
return writes;
|
|
22779
|
+
}
|
|
22780
|
+
/** New courses, as drafts; and the courses whose content changed, but for their publishing. */
|
|
22781
|
+
courseWrites() {
|
|
22782
|
+
return this.plan.courses.flatMap((course) => {
|
|
22783
|
+
const { server, folder } = course;
|
|
22784
|
+
if (!server) return [{ label: `course ${folder.slug} (create)`, requests: 1, apply: () => this.createCourse(folder) }];
|
|
22785
|
+
if (server.state !== "local" || server.server.published !== folder.published) return [];
|
|
22786
|
+
return [{ label: `course ${folder.slug}`, requests: 1, apply: () => this.updateCourse(folder, false) }];
|
|
22787
|
+
});
|
|
22788
|
+
}
|
|
22789
|
+
moduleWrites() {
|
|
22790
|
+
return this.plan.courses.flatMap((course) => course.modules.flatMap((module) => {
|
|
22791
|
+
const at = `${course.folder.slug}/${module.entry.key}`;
|
|
22792
|
+
if (!module.server) return [{ label: `module ${at} (create)`, requests: 1, apply: () => this.createModule(course.folder, module.entry) }];
|
|
22793
|
+
if (module.server.state !== "local") return [];
|
|
22794
|
+
return [{ label: `module ${at}`, requests: 1, apply: () => this.updateModule(course.folder, module.entry) }];
|
|
22795
|
+
}));
|
|
22796
|
+
}
|
|
22797
|
+
lessonWrites() {
|
|
22798
|
+
return this.lessons().flatMap((lesson) => {
|
|
22799
|
+
const { server, course, module, lesson: file } = lesson;
|
|
22800
|
+
const at = `${course.slug}/${file.slug}`;
|
|
22801
|
+
if (!server) return [{ label: `lesson ${at} (create)`, requests: 1, apply: () => lessonWrite(course, file, () => this.createLesson(course, module, file)) }];
|
|
22802
|
+
if (server.state !== "local") return [];
|
|
22803
|
+
const unpublished = server.server.published && !file.published;
|
|
22804
|
+
if (unpublished && !fileLesson(this.files.rooms, file).room_id?.startsWith(PENDING_ROOM)) return [];
|
|
22805
|
+
return [{ label: `lesson ${at}`, requests: 1, apply: () => lessonWrite(course, file, () => this.updateLesson(course, file, false)) }];
|
|
22806
|
+
});
|
|
22807
|
+
}
|
|
22808
|
+
/** How many position writes the plan predicts, every create landing as a new entity. */
|
|
22809
|
+
get plannedPlaces() {
|
|
22810
|
+
return this.predicted.moves.length;
|
|
22811
|
+
}
|
|
22812
|
+
/** The position writes, from the server's orders as the creates left them: when the phase is reached. */
|
|
22813
|
+
placeWrites() {
|
|
22814
|
+
return computePlaces(this.files, this.sim.clone(), realIds, this.placements).moves.map((move) => ({
|
|
22815
|
+
label: move.kind === "lesson" && this.sim.at.get(move.id) !== move.moduleId ? `lesson ${this.pathOf(move.id)} \u2192 module ${this.pathOf(move.moduleId)}` : `place of ${move.kind} ${this.pathOf(move.id)}`,
|
|
22816
|
+
requests: 1,
|
|
22817
|
+
apply: () => this.place(move)
|
|
22818
|
+
}));
|
|
22819
|
+
}
|
|
22820
|
+
/** Courses the files publish: last, once their lessons are in place. */
|
|
22821
|
+
publishWrites() {
|
|
22822
|
+
return this.plan.courses.flatMap((course) => {
|
|
22823
|
+
const { folder, server } = course;
|
|
22824
|
+
const now = folder.id === void 0 ? void 0 : this.courseNow.get(folder.id);
|
|
22825
|
+
if (!folder.published || now?.published || server !== null && server.state !== "local") return [];
|
|
22826
|
+
return [{ label: `course ${folder.slug} (publish)`, requests: 1, apply: () => this.updateCourse(folder, true) }];
|
|
22827
|
+
});
|
|
22828
|
+
}
|
|
22829
|
+
pathOf(id) {
|
|
22830
|
+
for (const course of this.files.courses) {
|
|
22831
|
+
if (course.id === id) return course.slug;
|
|
22832
|
+
for (const module of course.modules) {
|
|
22833
|
+
if (module.id === id) return `${course.slug}/${module.key}`;
|
|
22834
|
+
for (const lesson of module.lessons) if (lesson.id === id) return `${course.slug}/${lesson.slug}`;
|
|
22835
|
+
}
|
|
22836
|
+
}
|
|
22837
|
+
return this.name(id);
|
|
22838
|
+
}
|
|
22839
|
+
// ── Each write ──
|
|
22840
|
+
async createCourse(folder) {
|
|
22841
|
+
const made = await this.remote.createCourse({
|
|
22842
|
+
title: folder.title,
|
|
22843
|
+
description: folder.description,
|
|
22844
|
+
client_id: await createClientId(this.communityId, "course", folder.slug)
|
|
22845
|
+
});
|
|
22846
|
+
const twin = this.files.courses.find((other) => other !== folder && other.id === made.course_id);
|
|
22847
|
+
if (twin) {
|
|
22848
|
+
throw expected(
|
|
22849
|
+
`the name ${folder.slug} already names course ${made.course_id}, which your files also list as ${twin.slug}: a course folder's name keeps naming its course. Remove one of the two from ${COMMUNITY_FILE}'s courses.`
|
|
22850
|
+
);
|
|
22851
|
+
}
|
|
22852
|
+
const existed = this.sim.modules.get(made.course_id);
|
|
22853
|
+
folder.id = made.course_id;
|
|
22854
|
+
folder.rev = serverCourse(made).rev;
|
|
22855
|
+
this.courseNow.set(made.course_id, made);
|
|
22856
|
+
if (!existed) {
|
|
22857
|
+
append(this.sim.courses, made.course_id);
|
|
22858
|
+
this.sim.modules.set(made.course_id, freshOrder());
|
|
22859
|
+
}
|
|
22860
|
+
this.unsaved.add(folder);
|
|
22861
|
+
this.settle();
|
|
22862
|
+
await this.updateCourse(folder, false);
|
|
22863
|
+
}
|
|
22864
|
+
async updateCourse(folder, publish) {
|
|
22865
|
+
const now = this.courseNow.get(folder.id);
|
|
22866
|
+
const body = coursePatch(folder, now, publish);
|
|
22867
|
+
if (!body) return;
|
|
22868
|
+
const answer = await this.remote.updateCourse(folder.id, body);
|
|
22869
|
+
takeCourse(folder, answer, new Set(Object.keys(body)));
|
|
22870
|
+
folder.rev = serverCourse(answer).rev;
|
|
22871
|
+
this.courseNow.set(answer.course_id, answer);
|
|
22872
|
+
this.save.course(folder);
|
|
22873
|
+
}
|
|
22874
|
+
async createModule(course, entry) {
|
|
22875
|
+
const courseId = course.id;
|
|
22876
|
+
const made = await this.remote.createModule(courseId, {
|
|
22877
|
+
title: entry.title,
|
|
22878
|
+
client_id: await createClientId(this.communityId, "module", `${course.slug}/${entry.key}`)
|
|
22879
|
+
});
|
|
22880
|
+
if (made.course_id !== courseId) {
|
|
22881
|
+
throw expected(
|
|
22882
|
+
`the key ${entry.key} already names module ${show(made.title)} (${made.module_id}) of ${courseName(this.files, this.plan.server, made.course_id)}: a key keeps naming its module, and a module does not move between courses. Give the new module another key.`
|
|
22883
|
+
);
|
|
22884
|
+
}
|
|
22885
|
+
const twin = this.files.courses.flatMap((c) => c.modules.map((m) => ({ c, m }))).find(({ m }) => m !== entry && m.id === made.module_id);
|
|
22886
|
+
if (twin) {
|
|
22887
|
+
throw expected(`the key ${entry.key} already names module ${made.module_id}, which your files also list as ${twin.c.slug}/${twin.m.key}: remove one of the two entries.`);
|
|
22888
|
+
}
|
|
22889
|
+
const existed = this.sim.lessons.get(made.module_id);
|
|
22890
|
+
entry.id = made.module_id;
|
|
22891
|
+
entry.rev = serverModule(made).rev;
|
|
22892
|
+
this.moduleNow.set(made.module_id, made);
|
|
22893
|
+
if (!existed) {
|
|
22894
|
+
append(this.sim.modules.get(courseId), made.module_id);
|
|
22895
|
+
this.sim.lessons.set(made.module_id, freshOrder());
|
|
22896
|
+
}
|
|
22897
|
+
this.unsaved.add(course);
|
|
22898
|
+
this.settle();
|
|
22899
|
+
await this.updateModule(course, entry);
|
|
22900
|
+
}
|
|
22901
|
+
async updateModule(course, entry) {
|
|
22902
|
+
const now = this.moduleNow.get(entry.id);
|
|
22903
|
+
if (moduleContent(entry).title === moduleContent(now).title) return;
|
|
22904
|
+
const answer = await this.remote.updateModule(course.id, entry.id, { title: entry.title });
|
|
22905
|
+
if (moduleContent(answer).title !== moduleContent(entry).title) entry.title = answer.title;
|
|
22906
|
+
entry.rev = serverModule(answer).rev;
|
|
22907
|
+
this.moduleNow.set(answer.module_id, answer);
|
|
22908
|
+
this.save.course(course);
|
|
22909
|
+
}
|
|
22910
|
+
async createLesson(course, module, lesson) {
|
|
22911
|
+
const courseId = course.id;
|
|
22912
|
+
const roomId = fileLesson(this.files.rooms, lesson).room_id;
|
|
22913
|
+
if (roomId?.startsWith(PENDING_ROOM)) throw new Error(`createLesson: room ${lesson.room} has no id yet`);
|
|
22914
|
+
const made = await this.remote.createLesson(courseId, {
|
|
22915
|
+
module_id: module.id,
|
|
22916
|
+
title: lesson.title,
|
|
22917
|
+
...lesson.video_url === void 0 ? {} : { video_url: lesson.video_url },
|
|
22918
|
+
body: lesson.body,
|
|
22919
|
+
links: lesson.links,
|
|
22920
|
+
...roomId === null ? {} : { room_id: roomId },
|
|
22921
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
22922
|
+
published: lesson.published,
|
|
22923
|
+
client_id: await createClientId(this.communityId, "lesson", `${course.slug}/${lesson.slug}`)
|
|
22924
|
+
});
|
|
22925
|
+
if (made.course_id !== courseId) {
|
|
22926
|
+
throw expected(
|
|
22927
|
+
`the file name ${course.slug}/${lesson.slug} already names lesson ${show(made.title)} (${made.lesson_id}) of ${courseName(this.files, this.plan.server, made.course_id)}: a lesson's file name keeps naming its lesson, and a lesson does not move between courses. Give the new lesson another file name.`
|
|
22928
|
+
);
|
|
22929
|
+
}
|
|
22930
|
+
const twin = this.lessons().find((other) => other.lesson !== lesson && other.lesson.id === made.lesson_id);
|
|
22931
|
+
if (twin) {
|
|
22932
|
+
throw expected(`the file name ${lesson.slug} already names lesson ${made.lesson_id}, which your files also list as ${twin.course.slug}/${twin.lesson.slug}: remove one of the two.`);
|
|
22933
|
+
}
|
|
22934
|
+
const existed = this.sim.at.has(made.lesson_id);
|
|
22935
|
+
lesson.id = made.lesson_id;
|
|
22936
|
+
lesson.rev = serverLesson(made).rev;
|
|
22937
|
+
this.lessonNow.set(made.lesson_id, made);
|
|
22938
|
+
if (!existed) {
|
|
22939
|
+
append(this.sim.lessons.get(made.module_id), made.lesson_id);
|
|
22940
|
+
this.sim.at.set(made.lesson_id, made.module_id);
|
|
22941
|
+
}
|
|
22942
|
+
this.save.lesson(course, lesson);
|
|
22943
|
+
this.settle();
|
|
22944
|
+
await this.updateLesson(course, lesson, false);
|
|
22945
|
+
}
|
|
22946
|
+
async updateLesson(course, lesson, waitForRooms) {
|
|
22947
|
+
const now = this.lessonNow.get(lesson.id);
|
|
22948
|
+
const body = lessonPatch(this.files.rooms, lesson, now, waitForRooms);
|
|
22949
|
+
if (!body) return;
|
|
22950
|
+
const answer = await this.remote.updateLesson(course.id, lesson.id, body);
|
|
22951
|
+
takeLesson(this.files.rooms, lesson, answer, new Set(Object.keys(body)));
|
|
22952
|
+
lesson.rev = serverLesson(answer).rev;
|
|
22953
|
+
this.lessonNow.set(answer.lesson_id, answer);
|
|
22954
|
+
this.save.lesson(course, lesson);
|
|
22955
|
+
}
|
|
22956
|
+
/**
|
|
22957
|
+
* A position write. One whose answer is lost may have moved the child or not: the server is read
|
|
22958
|
+
* again, and when the child stands where the write put it, the write is recorded as if answered —
|
|
22959
|
+
* the orders it changed, and no other — before the failure stands. A refusal is not read back.
|
|
22960
|
+
*/
|
|
22961
|
+
async place(move) {
|
|
22962
|
+
try {
|
|
22963
|
+
await this.sendPlace(move);
|
|
22964
|
+
} catch (err) {
|
|
22965
|
+
if (outcomeUnknown(err)) await this.recordIfLanded(move).catch(() => void 0);
|
|
22966
|
+
throw err;
|
|
22967
|
+
}
|
|
22968
|
+
}
|
|
22969
|
+
async sendPlace(move) {
|
|
22970
|
+
if (move.kind === "course") {
|
|
22971
|
+
const answer = await this.remote.updateCourse(move.id, { position: move.position });
|
|
22972
|
+
this.placedCourse(answer, answer.position);
|
|
22973
|
+
} else if (move.kind === "module") {
|
|
22974
|
+
const answer = await this.remote.updateModule(move.courseId, move.id, { position: move.position });
|
|
22975
|
+
this.placedModule(move.courseId, answer, answer.position);
|
|
22976
|
+
} else {
|
|
22977
|
+
const from = this.sim.at.get(move.id);
|
|
22978
|
+
const body = from === move.moduleId ? { position: move.position } : { module_id: move.moduleId, position: move.position };
|
|
22979
|
+
const answer = await this.remote.updateLesson(move.courseId, move.id, body);
|
|
22980
|
+
this.placedLesson(answer, answer.module_id, answer.position);
|
|
22981
|
+
}
|
|
22982
|
+
}
|
|
22983
|
+
/** A position write whose answer was lost, recorded when a read finds its child where it put it. */
|
|
22984
|
+
async recordIfLanded(move) {
|
|
22985
|
+
const wanted = (order) => {
|
|
22986
|
+
const ids = [...order.server];
|
|
22987
|
+
placeAt(ids, move.id, move.position);
|
|
22988
|
+
return ids.indexOf(move.id);
|
|
22989
|
+
};
|
|
22990
|
+
if (move.kind === "course") {
|
|
22991
|
+
const courses = await this.remote.listCourses();
|
|
22992
|
+
const at2 = courses.findIndex((course2) => course2.course_id === move.id);
|
|
22993
|
+
if (at2 >= 0 && at2 === wanted(this.sim.courses)) this.placedCourse(courses[at2], at2);
|
|
22994
|
+
return;
|
|
22995
|
+
}
|
|
22996
|
+
const course = await this.remote.exportCourse(move.courseId);
|
|
22997
|
+
if (move.kind === "module") {
|
|
22998
|
+
const at2 = course.modules.findIndex((module) => module.module_id === move.id);
|
|
22999
|
+
if (at2 >= 0 && at2 === wanted(this.sim.modules.get(move.courseId))) this.placedModule(move.courseId, course.modules[at2], at2);
|
|
23000
|
+
return;
|
|
23001
|
+
}
|
|
23002
|
+
const lessons = course.modules.find((module) => module.module_id === move.moduleId)?.lessons ?? [];
|
|
23003
|
+
const at = lessons.findIndex((lesson) => lesson.lesson_id === move.id);
|
|
23004
|
+
if (at >= 0 && at === wanted(this.sim.lessons.get(move.moduleId))) this.placedLesson(lessons[at], move.moduleId, at);
|
|
23005
|
+
}
|
|
23006
|
+
placedCourse(now, position) {
|
|
23007
|
+
shift(this.sim.courses, now.course_id, position);
|
|
23008
|
+
this.courseNow.set(now.course_id, now);
|
|
23009
|
+
this.settle();
|
|
23010
|
+
}
|
|
23011
|
+
placedModule(courseId, now, position) {
|
|
23012
|
+
shift(this.sim.modules.get(courseId), now.module_id, position);
|
|
23013
|
+
this.moduleNow.set(now.module_id, now);
|
|
23014
|
+
this.settle();
|
|
23015
|
+
}
|
|
23016
|
+
placedLesson(now, moduleId, position) {
|
|
23017
|
+
const from = this.sim.at.get(now.lesson_id);
|
|
23018
|
+
if (moduleId !== from) leave(this.sim.lessons.get(from), now.lesson_id);
|
|
23019
|
+
shift(this.sim.lessons.get(moduleId), now.lesson_id, position);
|
|
23020
|
+
this.sim.at.set(now.lesson_id, moduleId);
|
|
23021
|
+
this.lessonNow.set(now.lesson_id, now);
|
|
23022
|
+
this.settle();
|
|
23023
|
+
}
|
|
23024
|
+
/**
|
|
23025
|
+
* After a write that changed a parent's order (or a refresh), each such parent's synced list is
|
|
23026
|
+
* recorded and the files keeping a changed one are saved. A synced list is the server's order as
|
|
23027
|
+
* its own placement leaves it — less a lesson the server moved in since, and with one it moved
|
|
23028
|
+
* out, which the files have not taken: their base stays the module the files last synced them in
|
|
23029
|
+
* — or, for a parent whose order changed on the server only, the files' synced list as push left
|
|
23030
|
+
* it (`OrderSim.pinned`).
|
|
23031
|
+
*/
|
|
23032
|
+
settle() {
|
|
23033
|
+
const record = (order, current, base, set, owner) => {
|
|
23034
|
+
if (!order?.touched || order.conflict) return;
|
|
23035
|
+
order.touched = false;
|
|
23036
|
+
const ids = order.pinned ?? base(order, current ?? []);
|
|
23037
|
+
if (current && sameOrder(current, ids)) return;
|
|
23038
|
+
set([...ids]);
|
|
23039
|
+
this.unsaved.add(owner);
|
|
23040
|
+
};
|
|
23041
|
+
const asIs = (order) => order.server;
|
|
23042
|
+
record(this.sim.courses, this.files.synced_courses, asIs, (ids) => {
|
|
23043
|
+
this.files.synced_courses = ids;
|
|
23044
|
+
}, null);
|
|
23045
|
+
for (const course of this.files.courses) {
|
|
23046
|
+
const modules = course.id === void 0 ? void 0 : this.sim.modules.get(course.id);
|
|
23047
|
+
if (!modules) continue;
|
|
23048
|
+
const synced2 = () => course.synced ??= { lessons: {} };
|
|
23049
|
+
record(modules, course.synced?.modules, asIs, (ids) => {
|
|
23050
|
+
synced2().modules = ids;
|
|
23051
|
+
}, course);
|
|
23052
|
+
for (const moduleId of modules.server) {
|
|
23053
|
+
record(this.sim.lessons.get(moduleId), course.synced?.lessons[moduleId], (order, current) => this.lessonsBase(moduleId, order.server, current), (ids) => {
|
|
23054
|
+
synced2().lessons[moduleId] = ids;
|
|
23055
|
+
}, course);
|
|
23056
|
+
}
|
|
23057
|
+
}
|
|
23058
|
+
this.flush();
|
|
23059
|
+
}
|
|
23060
|
+
/**
|
|
23061
|
+
* A module's synced lessons: the server's, less those it moved in and with those it moved out, the
|
|
23062
|
+
* files not having taken either — each one moved out back in its place in the synced list as it
|
|
23063
|
+
* stood (`current`).
|
|
23064
|
+
*/
|
|
23065
|
+
lessonsBase(moduleId, server, current) {
|
|
23066
|
+
const movedIn = this.movedIn.get(moduleId);
|
|
23067
|
+
const ids = server.filter((id) => !movedIn?.has(id));
|
|
23068
|
+
for (const id of this.movedOut.get(moduleId) ?? []) if (!ids.includes(id)) insertAfter(ids, id, current);
|
|
23069
|
+
return ids;
|
|
23070
|
+
}
|
|
23071
|
+
};
|
|
23072
|
+
synced = (state, takes) => state !== "conflict" || takes(state);
|
|
23073
|
+
}
|
|
23074
|
+
});
|
|
23075
|
+
|
|
21108
23076
|
// src/communities/sync.ts
|
|
21109
23077
|
var sync_exports2 = {};
|
|
21110
23078
|
__export(sync_exports2, {
|
|
21111
23079
|
pullCommunity: () => pullCommunity,
|
|
21112
23080
|
pushCommunity: () => pushCommunity
|
|
21113
23081
|
});
|
|
21114
|
-
import * as
|
|
21115
|
-
import * as
|
|
23082
|
+
import * as fs19 from "fs";
|
|
23083
|
+
import * as path27 from "path";
|
|
21116
23084
|
function parseArgs5(args2) {
|
|
21117
23085
|
return {
|
|
21118
23086
|
autoConfirm: args2.includes("--yes") || args2.includes("-y"),
|
|
@@ -21173,11 +23141,22 @@ function planSync(files, server) {
|
|
|
21173
23141
|
},
|
|
21174
23142
|
rooms,
|
|
21175
23143
|
untracked: server.rooms.filter((room) => !tracked.has(room.room_id)),
|
|
21176
|
-
unknownIds
|
|
23144
|
+
unknownIds,
|
|
23145
|
+
courses: planCourses(files, server.courses)
|
|
21177
23146
|
};
|
|
21178
23147
|
}
|
|
21179
23148
|
function unknownIdProblems(plan) {
|
|
21180
|
-
return plan.unknownIds.map((entry) => `room ${entry.key}: ${entry.id} is not a room of this community`);
|
|
23149
|
+
return [...plan.unknownIds.map((entry) => `room ${entry.key}: ${entry.id} is not a room of this community`), ...plan.courses.problems];
|
|
23150
|
+
}
|
|
23151
|
+
function warnUnlisted(files) {
|
|
23152
|
+
for (const folder of files.unlistedCourses) {
|
|
23153
|
+
console.warn(`Skipped: ${unlistedPath(folder.name)} \u2014 ${COMMUNITY_FILE}'s courses does not list it.`);
|
|
23154
|
+
}
|
|
23155
|
+
for (const course of files.courses) {
|
|
23156
|
+
for (const file of course.unlisted) {
|
|
23157
|
+
console.warn(`Skipped: ${unlistedPath(file.name, course)} \u2014 no module of the course lists it.`);
|
|
23158
|
+
}
|
|
23159
|
+
}
|
|
21181
23160
|
}
|
|
21182
23161
|
function settingsChanges(from, to) {
|
|
21183
23162
|
const out = [];
|
|
@@ -21186,31 +23165,6 @@ function settingsChanges(from, to) {
|
|
|
21186
23165
|
if (from.rules !== to.rules) out.push("rules (rules.md)");
|
|
21187
23166
|
return out;
|
|
21188
23167
|
}
|
|
21189
|
-
function lineDiff(from, to) {
|
|
21190
|
-
const a = from ? sanitizeTerminalText(from).split("\n") : [];
|
|
21191
|
-
const b = to ? sanitizeTerminalText(to).split("\n") : [];
|
|
21192
|
-
if (a.length * b.length > 25e4) return [...a.map((l) => `- ${l}`), ...b.map((l) => `+ ${l}`)];
|
|
21193
|
-
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
21194
|
-
for (let i2 = a.length - 1; i2 >= 0; i2--) {
|
|
21195
|
-
for (let j2 = b.length - 1; j2 >= 0; j2--) {
|
|
21196
|
-
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
21197
|
-
}
|
|
21198
|
-
}
|
|
21199
|
-
const out = [];
|
|
21200
|
-
let i = 0;
|
|
21201
|
-
let j = 0;
|
|
21202
|
-
while (i < a.length || j < b.length) {
|
|
21203
|
-
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
21204
|
-
i++;
|
|
21205
|
-
j++;
|
|
21206
|
-
} else if (j >= b.length || i < a.length && lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
21207
|
-
out.push(`- ${a[i++]}`);
|
|
21208
|
-
} else {
|
|
21209
|
-
out.push(`+ ${b[j++]}`);
|
|
21210
|
-
}
|
|
21211
|
-
}
|
|
21212
|
-
return out;
|
|
21213
|
-
}
|
|
21214
23168
|
function roomChanges(from, to) {
|
|
21215
23169
|
const out = [];
|
|
21216
23170
|
if (from.name !== to.name) out.push(`name ${show(from.name)} \u2192 ${show(to.name)}`);
|
|
@@ -21220,19 +23174,6 @@ function roomChanges(from, to) {
|
|
|
21220
23174
|
function joinLinkChange(from, to) {
|
|
21221
23175
|
return to ? `${onOff(from)} \u2192 on: a new link \u2014 every link shared before stays dead` : `${onOff(from)} \u2192 off: every link shared before stops working`;
|
|
21222
23176
|
}
|
|
21223
|
-
function printSection(title, lines) {
|
|
21224
|
-
if (lines.length === 0) return;
|
|
21225
|
-
console.log(`
|
|
21226
|
-
${title}`);
|
|
21227
|
-
for (const line of lines) console.log(` ${line}`);
|
|
21228
|
-
}
|
|
21229
|
-
function printListing(listing) {
|
|
21230
|
-
printSection("Changes:", listing.changes);
|
|
21231
|
-
printSection("Changed on the server since your last pull \u2014 left as is; pull to take it:", listing.serverChanged);
|
|
21232
|
-
printSection("Kept \u2014 edited in your files, not pushed yet:", listing.kept);
|
|
21233
|
-
printSection("Not in your files \u2014 left as is:", listing.leftAsIs);
|
|
21234
|
-
printSection("Conflicts \u2014 changed in your files and on the server since your last pull:", listing.conflicts);
|
|
21235
|
-
}
|
|
21236
23177
|
function pushProblems(files, server, plan) {
|
|
21237
23178
|
const problems2 = unknownIdProblems(plan);
|
|
21238
23179
|
if (files.name !== server.community.name) {
|
|
@@ -21244,12 +23185,13 @@ function pushProblems(files, server, plan) {
|
|
|
21244
23185
|
}
|
|
21245
23186
|
}
|
|
21246
23187
|
const creates = plan.rooms.filter((room) => room.kind === "create").length;
|
|
21247
|
-
const
|
|
21248
|
-
if (server.rooms.length +
|
|
23188
|
+
const certainlyNew2 = Math.max(0, creates - plan.untracked.length);
|
|
23189
|
+
if (server.rooms.length + certainlyNew2 > COMMUNITY_ROOMS_MAX) {
|
|
21249
23190
|
problems2.push(
|
|
21250
23191
|
`rooms: a community holds at most ${COMMUNITY_ROOMS_MAX} rooms, archived ones included; it has ${server.rooms.length}, and the files create ${creates}`
|
|
21251
23192
|
);
|
|
21252
23193
|
}
|
|
23194
|
+
problems2.push(...courseCapProblems(files, plan.courses));
|
|
21253
23195
|
return problems2;
|
|
21254
23196
|
}
|
|
21255
23197
|
function settingsPatch(files, server) {
|
|
@@ -21289,7 +23231,18 @@ function takeJoinLink(files, enabled) {
|
|
|
21289
23231
|
files.join_link_enabled = enabled;
|
|
21290
23232
|
files.join_link_rev = joinLinkRev(files.community_id, { enabled });
|
|
21291
23233
|
}
|
|
21292
|
-
function
|
|
23234
|
+
function pushPhases(files, plan, remote, save, courses) {
|
|
23235
|
+
return [
|
|
23236
|
+
courses.unpublishWrites(),
|
|
23237
|
+
communityWrites(files, plan, remote, save),
|
|
23238
|
+
courses.courseWrites(),
|
|
23239
|
+
courses.moduleWrites(),
|
|
23240
|
+
courses.lessonWrites(),
|
|
23241
|
+
{ planned: courses.plannedPlaces, later: () => courses.placeWrites() },
|
|
23242
|
+
{ planned: courses.publishWrites().length, later: () => courses.publishWrites() }
|
|
23243
|
+
];
|
|
23244
|
+
}
|
|
23245
|
+
function communityWrites(files, plan, remote, save) {
|
|
21293
23246
|
const writes = [];
|
|
21294
23247
|
if (plan.settings.state === "local") {
|
|
21295
23248
|
const body = settingsPatch(files, plan.settings.server);
|
|
@@ -21379,8 +23332,8 @@ function refreshConverged(files, plan) {
|
|
|
21379
23332
|
}
|
|
21380
23333
|
return refreshed;
|
|
21381
23334
|
}
|
|
21382
|
-
function pushListing(plan) {
|
|
21383
|
-
const listing =
|
|
23335
|
+
function pushListing(plan, courses) {
|
|
23336
|
+
const listing = emptyListing();
|
|
21384
23337
|
const { settings, joinLink } = plan;
|
|
21385
23338
|
if (settings.state === "local") {
|
|
21386
23339
|
listing.changes.push(`~ settings: ${settingsChanges(settings.server, settings.file).join("; ")}`);
|
|
@@ -21405,6 +23358,7 @@ function pushListing(plan) {
|
|
|
21405
23358
|
listing.serverChanged = named("remote");
|
|
21406
23359
|
listing.conflicts = named("conflict");
|
|
21407
23360
|
listing.leftAsIs = plan.untracked.map((room) => `room ${show(room.name)} (${room.room_id})`);
|
|
23361
|
+
courses.list(listing);
|
|
21408
23362
|
return listing;
|
|
21409
23363
|
}
|
|
21410
23364
|
async function pushCommunity(gitRootOrNull, selector, args2) {
|
|
@@ -21423,37 +23377,39 @@ async function pushCommunity(gitRootOrNull, selector, args2) {
|
|
|
21423
23377
|
throw expected("Push changes the live community and asks first: pass -y when not running in a terminal, or --dry-run to only list the changes.");
|
|
21424
23378
|
}
|
|
21425
23379
|
const { organization_id: orgId } = requireRepoConfig(gitRoot);
|
|
21426
|
-
const label =
|
|
23380
|
+
const label = path27.basename(folder);
|
|
21427
23381
|
assertInScope("communities", files.community_id, label);
|
|
23382
|
+
warnUnlisted(files);
|
|
21428
23383
|
const remote = await openRemote(files.community_id);
|
|
21429
23384
|
const server = await remote.read(orgId);
|
|
21430
23385
|
const plan = planSync(files, server);
|
|
21431
|
-
console.log(`Community ${show(server.community.name)} (${server.community.community_id}) \u2014 ${
|
|
23386
|
+
console.log(`Community ${show(server.community.name)} (${server.community.community_id}) \u2014 ${path27.relative(gitRoot, folder)}/`);
|
|
21432
23387
|
const problems2 = pushProblems(files, server, plan);
|
|
21433
23388
|
if (problems2.length > 0) {
|
|
21434
23389
|
throw expected(`Nothing was pushed:
|
|
21435
23390
|
${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
21436
23391
|
}
|
|
21437
|
-
const
|
|
23392
|
+
const { communitiesDir } = resolveLayout(gitRoot);
|
|
23393
|
+
const save = () => {
|
|
23394
|
+
writeCommunityFiles(communitiesDir, folder, files);
|
|
23395
|
+
};
|
|
23396
|
+
const courses = new CoursePush(files, plan.courses, remote, courseSaver(communitiesDir, folder, save));
|
|
23397
|
+
const listing = pushListing(plan, courses);
|
|
21438
23398
|
printListing(listing);
|
|
21439
23399
|
if (listing.conflicts.length > 0) {
|
|
21440
23400
|
throw expected(`
|
|
21441
23401
|
Nothing was pushed: ${listing.conflicts.length} conflict(s). ${resolveConflicts(folder)}`);
|
|
21442
23402
|
}
|
|
21443
|
-
const
|
|
21444
|
-
const
|
|
21445
|
-
|
|
21446
|
-
|
|
21447
|
-
const writes = pushWrites(files, plan, remote, save);
|
|
21448
|
-
if (writes.length === 0) {
|
|
21449
|
-
const refreshed = dryRun ? 0 : refreshConverged(files, plan);
|
|
23403
|
+
const phases = pushPhases(files, plan, remote, save, courses);
|
|
23404
|
+
const requests = phases.reduce((sum, phase) => sum + plannedRequests(phase), 0);
|
|
23405
|
+
if (requests === 0) {
|
|
23406
|
+
const refreshed = dryRun ? 0 : refreshConverged(files, plan) + courses.refreshConverged();
|
|
21450
23407
|
if (refreshed > 0) save();
|
|
21451
23408
|
console.log(`
|
|
21452
|
-
No changes to push${refreshed > 0 ? ` (${refreshed} rev(s) refreshed
|
|
23409
|
+
No changes to push${refreshed > 0 ? ` (${refreshed} rev(s) refreshed)` : ""}.`);
|
|
21453
23410
|
if (!dryRun) seedScopeIfEmpty("communities", files.community_id);
|
|
21454
23411
|
return;
|
|
21455
23412
|
}
|
|
21456
|
-
const requests = writes.reduce((sum, write) => sum + write.requests, 0);
|
|
21457
23413
|
console.log(
|
|
21458
23414
|
`
|
|
21459
23415
|
This push makes ${requests} request(s) to the live community \u2014 ${paceDuration(requests)} at ${COMMUNITY_REQUESTS_PER_MINUTE} a minute.`
|
|
@@ -21475,24 +23431,67 @@ This push makes ${requests} request(s) to the live community \u2014 ${paceDurati
|
|
|
21475
23431
|
}
|
|
21476
23432
|
}
|
|
21477
23433
|
refreshConverged(files, plan);
|
|
23434
|
+
courses.refreshConverged();
|
|
21478
23435
|
save();
|
|
21479
|
-
|
|
21480
|
-
|
|
21481
|
-
|
|
21482
|
-
|
|
21483
|
-
|
|
21484
|
-
|
|
21485
|
-
|
|
21486
|
-
)
|
|
23436
|
+
let total = phases.reduce((sum, phase) => sum + plannedCount(phase), 0);
|
|
23437
|
+
let done = 0;
|
|
23438
|
+
const stop = stopAfterWrite();
|
|
23439
|
+
try {
|
|
23440
|
+
for (const phase of phases) {
|
|
23441
|
+
const writes = Array.isArray(phase) ? phase : phase.later();
|
|
23442
|
+
total += writes.length - plannedCount(phase);
|
|
23443
|
+
for (const write of writes) {
|
|
23444
|
+
if (stop.requested()) {
|
|
23445
|
+
throw expected(
|
|
23446
|
+
`Push stopped on request after ${done} change(s). What it applied is on the community, and the files record it: push again to go on.`
|
|
23447
|
+
);
|
|
23448
|
+
}
|
|
23449
|
+
try {
|
|
23450
|
+
await write.apply();
|
|
23451
|
+
} catch (err) {
|
|
23452
|
+
throw expected(
|
|
23453
|
+
`Push stopped at ${write.label}: ${stopReason(err)}
|
|
23454
|
+
What it applied before stopping is on the community, and the files record it. Fix the cause and push again.`
|
|
23455
|
+
);
|
|
23456
|
+
}
|
|
23457
|
+
console.log(` [${++done}/${total}] ${write.label}`);
|
|
23458
|
+
}
|
|
21487
23459
|
}
|
|
21488
|
-
|
|
23460
|
+
} finally {
|
|
23461
|
+
stop.release();
|
|
21489
23462
|
}
|
|
21490
23463
|
console.log(
|
|
21491
23464
|
`
|
|
21492
|
-
Pushed ${
|
|
23465
|
+
Pushed ${done} change(s). Commit ${path27.relative(gitRoot, folder)}/: the files now record the ids and revs this push wrote.`
|
|
21493
23466
|
);
|
|
21494
23467
|
seedScopeIfEmpty("communities", files.community_id);
|
|
21495
23468
|
}
|
|
23469
|
+
function stopAfterWrite() {
|
|
23470
|
+
let asked = false;
|
|
23471
|
+
const onSignal = (signal) => {
|
|
23472
|
+
if (asked) process.exit(SIGNAL_EXIT_CODE[signal]);
|
|
23473
|
+
asked = true;
|
|
23474
|
+
console.error("\n Stopping after the write in progress, once the files record it. Press Ctrl-C again to leave at once.");
|
|
23475
|
+
};
|
|
23476
|
+
for (const signal of HANDLED_SIGNALS) process.on(signal, onSignal);
|
|
23477
|
+
return {
|
|
23478
|
+
requested: () => asked,
|
|
23479
|
+
release: () => {
|
|
23480
|
+
for (const signal of HANDLED_SIGNALS) process.off(signal, onSignal);
|
|
23481
|
+
}
|
|
23482
|
+
};
|
|
23483
|
+
}
|
|
23484
|
+
function courseSaver(root, folder, community) {
|
|
23485
|
+
return {
|
|
23486
|
+
community,
|
|
23487
|
+
course: (course) => {
|
|
23488
|
+
writeCourseYaml(root, folder, course);
|
|
23489
|
+
},
|
|
23490
|
+
lesson: (course, lesson) => {
|
|
23491
|
+
writeLessonFile(root, folder, course, lesson);
|
|
23492
|
+
}
|
|
23493
|
+
};
|
|
23494
|
+
}
|
|
21496
23495
|
function changedSince(before, after) {
|
|
21497
23496
|
const id = before.community.community_id;
|
|
21498
23497
|
const changed = [];
|
|
@@ -21504,6 +23503,7 @@ function changedSince(before, after) {
|
|
|
21504
23503
|
for (const room of after.rooms) {
|
|
21505
23504
|
if (revs.get(room.room_id) !== serverRoom(room).rev) changed.push(`room ${show(room.name)}`);
|
|
21506
23505
|
}
|
|
23506
|
+
changed.push(...coursesChangedSince(before.courses, after.courses));
|
|
21507
23507
|
return changed;
|
|
21508
23508
|
}
|
|
21509
23509
|
function stopReason(err) {
|
|
@@ -21527,15 +23527,18 @@ function filesFromServer(server) {
|
|
|
21527
23527
|
settings: { slow_mode_seconds: 0, courses_label: "" },
|
|
21528
23528
|
rules: "",
|
|
21529
23529
|
join_link_enabled: false,
|
|
21530
|
-
rooms: []
|
|
23530
|
+
rooms: [],
|
|
23531
|
+
courses: [],
|
|
23532
|
+
unlistedCourses: []
|
|
21531
23533
|
};
|
|
21532
23534
|
takeSettings(files, server.settings);
|
|
21533
23535
|
takeJoinLink(files, server.joinLink.enabled);
|
|
21534
23536
|
addUntracked(files, server.rooms);
|
|
23537
|
+
coursesFromServer(files, server.courses);
|
|
21535
23538
|
return files;
|
|
21536
23539
|
}
|
|
21537
23540
|
function pullMerge(files, server, plan, theirs) {
|
|
21538
|
-
const listing =
|
|
23541
|
+
const listing = emptyListing();
|
|
21539
23542
|
const takes = (state) => state === "remote" || state === "conflict" && theirs;
|
|
21540
23543
|
const note = (state, what) => {
|
|
21541
23544
|
if (state === "local") listing.kept.push(what);
|
|
@@ -21573,6 +23576,7 @@ function pullMerge(files, server, plan, theirs) {
|
|
|
21573
23576
|
}
|
|
21574
23577
|
refreshConverged(files, plan);
|
|
21575
23578
|
listing.changes.push(...addUntracked(files, plan.untracked));
|
|
23579
|
+
pullCourses(files, plan.courses, takes, note, listing);
|
|
21576
23580
|
return listing;
|
|
21577
23581
|
}
|
|
21578
23582
|
async function pullCommunity(gitRootOrNull, selector, args2) {
|
|
@@ -21588,26 +23592,28 @@ async function pullCommunity(gitRootOrNull, selector, args2) {
|
|
|
21588
23592
|
communityId = existing.files.community_id;
|
|
21589
23593
|
}
|
|
21590
23594
|
const { organization_id: orgId } = requireRepoConfig(gitRoot);
|
|
21591
|
-
assertInScope("communities", communityId, existing ?
|
|
23595
|
+
assertInScope("communities", communityId, existing ? path27.basename(existing.folder) : void 0);
|
|
21592
23596
|
const { communitiesDir } = resolveLayout(gitRoot);
|
|
21593
23597
|
const remote = await openRemote(communityId);
|
|
21594
23598
|
const server = await remote.read(orgId);
|
|
21595
23599
|
if (!existing) {
|
|
21596
23600
|
const taken = new Set(safeReaddir2(communitiesDir));
|
|
21597
|
-
const folder2 =
|
|
23601
|
+
const folder2 = path27.join(communitiesDir, localName(server.community.name, communityId, taken, "community"));
|
|
21598
23602
|
const files = filesFromServer(server);
|
|
21599
|
-
const where = `${
|
|
23603
|
+
const where = `${path27.relative(gitRoot, folder2)}/`;
|
|
23604
|
+
const counts = `${files.rooms.length} room(s), ${files.courses.length} course(s)`;
|
|
21600
23605
|
if (dryRun) {
|
|
21601
|
-
console.log(`Dry run \u2014 would write community ${show(server.community.name)} to ${where} with ${
|
|
23606
|
+
console.log(`Dry run \u2014 would write community ${show(server.community.name)} to ${where} with ${counts}.`);
|
|
21602
23607
|
return;
|
|
21603
23608
|
}
|
|
21604
23609
|
requireRealSubdirNoSymlink(gitRoot, communitiesDir, true);
|
|
21605
23610
|
writeCommunityFolder(communitiesDir, folder2, files);
|
|
21606
|
-
console.log(`Community ${show(server.community.name)} written to ${where} (${
|
|
23611
|
+
console.log(`Community ${show(server.community.name)} written to ${where} (${counts}). Commit it: its ids and revs are the base the next push compares against.`);
|
|
21607
23612
|
seedScopeIfEmpty("communities", communityId);
|
|
21608
23613
|
return;
|
|
21609
23614
|
}
|
|
21610
23615
|
const { folder, files: local } = existing;
|
|
23616
|
+
warnUnlisted(local);
|
|
21611
23617
|
const plan = planSync(local, server);
|
|
21612
23618
|
const problems2 = unknownIdProblems(plan);
|
|
21613
23619
|
if (problems2.length > 0) {
|
|
@@ -21615,7 +23621,7 @@ async function pullCommunity(gitRootOrNull, selector, args2) {
|
|
|
21615
23621
|
${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
21616
23622
|
}
|
|
21617
23623
|
const listing = pullMerge(local, server, plan, theirs);
|
|
21618
|
-
console.log(`Community ${show(server.community.name)} (${communityId}) \u2014 ${
|
|
23624
|
+
console.log(`Community ${show(server.community.name)} (${communityId}) \u2014 ${path27.relative(gitRoot, folder)}/`);
|
|
21619
23625
|
printListing(listing);
|
|
21620
23626
|
if (dryRun) {
|
|
21621
23627
|
console.log("\nDry run \u2014 nothing was written.");
|
|
@@ -21630,16 +23636,16 @@ Wrote ${written.join(", ")}.` : "\nAlready up to date.");
|
|
|
21630
23636
|
}
|
|
21631
23637
|
}
|
|
21632
23638
|
function resolveConflicts(folder) {
|
|
21633
|
-
return `To resolve, take the server's side \u2014 wayai pull ${WAYAI_LAYOUT.communitiesSubdir}/${
|
|
23639
|
+
return `To resolve, take the server's side \u2014 wayai pull ${WAYAI_LAYOUT.communitiesSubdir}/${path27.basename(folder)} --theirs \u2014 then make again any edit of yours to keep, and push.`;
|
|
21634
23640
|
}
|
|
21635
23641
|
function safeReaddir2(dir) {
|
|
21636
23642
|
try {
|
|
21637
|
-
return
|
|
23643
|
+
return fs19.readdirSync(dir);
|
|
21638
23644
|
} catch {
|
|
21639
23645
|
return [];
|
|
21640
23646
|
}
|
|
21641
23647
|
}
|
|
21642
|
-
var
|
|
23648
|
+
var Remote, onOff, plannedCount, plannedRequests;
|
|
21643
23649
|
var init_sync2 = __esm({
|
|
21644
23650
|
"src/communities/sync.ts"() {
|
|
21645
23651
|
"use strict";
|
|
@@ -21655,11 +23661,14 @@ var init_sync2 = __esm({
|
|
|
21655
23661
|
init_worktree_scope();
|
|
21656
23662
|
init_community_workspace();
|
|
21657
23663
|
init_helpers();
|
|
23664
|
+
init_eval_session_control();
|
|
21658
23665
|
init_api2();
|
|
21659
23666
|
init_pacer();
|
|
21660
23667
|
init_files();
|
|
23668
|
+
init_course_files();
|
|
23669
|
+
init_courses();
|
|
23670
|
+
init_listing();
|
|
21661
23671
|
init_model();
|
|
21662
|
-
show = (text) => JSON.stringify(sanitizeTerminalText(text));
|
|
21663
23672
|
Remote = class {
|
|
21664
23673
|
constructor(api, pacer, communityId) {
|
|
21665
23674
|
this.api = api;
|
|
@@ -21670,8 +23679,9 @@ var init_sync2 = __esm({
|
|
|
21670
23679
|
return (await callCommunities(this.pacer.run(request), notFound)).data;
|
|
21671
23680
|
}
|
|
21672
23681
|
/**
|
|
21673
|
-
*
|
|
21674
|
-
*
|
|
23682
|
+
* gh #5003 Decision 8's bounded read: the org's list, settings, rooms, the join link and the
|
|
23683
|
+
* courses, then each course whole — one request per course. The org's list both binds the
|
|
23684
|
+
* community to the workspace's organization and names it.
|
|
21675
23685
|
*/
|
|
21676
23686
|
async read(orgId) {
|
|
21677
23687
|
const communities = await this.send(() => this.api.listOrgCommunities(orgId), orgNotFound(orgId));
|
|
@@ -21681,12 +23691,14 @@ var init_sync2 = __esm({
|
|
|
21681
23691
|
`Community ${this.communityId} is not one of the communities of organization ${orgId}, the one ${WORKSPACE_MANIFEST_LABEL} names. A community is pulled and pushed from a workspace bound to the organization that owns it.`
|
|
21682
23692
|
);
|
|
21683
23693
|
}
|
|
21684
|
-
const [settings, rooms, joinLink] = await Promise.all([
|
|
23694
|
+
const [settings, rooms, joinLink, courses] = await Promise.all([
|
|
21685
23695
|
this.send(() => this.api.getCommunitySettings(this.communityId)),
|
|
21686
23696
|
this.send(() => this.api.listCommunityRooms(this.communityId)),
|
|
21687
|
-
this.send(() => this.api.getCommunityJoinLink(this.communityId))
|
|
23697
|
+
this.send(() => this.api.getCommunityJoinLink(this.communityId)),
|
|
23698
|
+
// Each course whole, as soon as the list names it.
|
|
23699
|
+
this.listCourses().then((list) => Promise.all(list.map((course) => this.exportCourse(course.course_id))))
|
|
21688
23700
|
]);
|
|
21689
|
-
return { community, settings, rooms, joinLink };
|
|
23701
|
+
return { community, settings, rooms, joinLink, courses: new ServerCourses(courses) };
|
|
21690
23702
|
}
|
|
21691
23703
|
updateSettings(body) {
|
|
21692
23704
|
return this.send(() => this.api.updateCommunitySettings(this.communityId, body));
|
|
@@ -21701,8 +23713,34 @@ var init_sync2 = __esm({
|
|
|
21701
23713
|
updateRoom(roomId, body) {
|
|
21702
23714
|
return this.send(() => this.api.updateCommunityRoom(this.communityId, roomId, body));
|
|
21703
23715
|
}
|
|
23716
|
+
createCourse(body) {
|
|
23717
|
+
return this.send(() => this.api.createCommunityCourse(this.communityId, body));
|
|
23718
|
+
}
|
|
23719
|
+
updateCourse(courseId, body) {
|
|
23720
|
+
return this.send(() => this.api.updateCommunityCourse(this.communityId, courseId, body));
|
|
23721
|
+
}
|
|
23722
|
+
createModule(courseId, body) {
|
|
23723
|
+
return this.send(() => this.api.createCommunityModule(this.communityId, courseId, body));
|
|
23724
|
+
}
|
|
23725
|
+
updateModule(courseId, moduleId, body) {
|
|
23726
|
+
return this.send(() => this.api.updateCommunityModule(this.communityId, courseId, moduleId, body));
|
|
23727
|
+
}
|
|
23728
|
+
createLesson(courseId, body) {
|
|
23729
|
+
return this.send(() => this.api.createCommunityLesson(this.communityId, courseId, body));
|
|
23730
|
+
}
|
|
23731
|
+
updateLesson(courseId, lessonId, body) {
|
|
23732
|
+
return this.send(() => this.api.updateCommunityLesson(this.communityId, courseId, lessonId, body));
|
|
23733
|
+
}
|
|
23734
|
+
listCourses() {
|
|
23735
|
+
return this.send(() => this.api.listCommunityCourseDrafts(this.communityId));
|
|
23736
|
+
}
|
|
23737
|
+
exportCourse(courseId) {
|
|
23738
|
+
return this.send(() => this.api.exportCommunityCourse(this.communityId, courseId));
|
|
23739
|
+
}
|
|
21704
23740
|
};
|
|
21705
23741
|
onOff = (enabled) => enabled ? "on" : "off";
|
|
23742
|
+
plannedCount = (phase) => Array.isArray(phase) ? phase.length : phase.planned;
|
|
23743
|
+
plannedRequests = (phase) => Array.isArray(phase) ? phase.reduce((n, write) => n + write.requests, 0) : phase.planned;
|
|
21706
23744
|
}
|
|
21707
23745
|
});
|
|
21708
23746
|
|
|
@@ -21721,9 +23759,9 @@ __export(push_exports, {
|
|
|
21721
23759
|
shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel,
|
|
21722
23760
|
syncAfterPush: () => syncAfterPush
|
|
21723
23761
|
});
|
|
21724
|
-
import * as
|
|
21725
|
-
import * as
|
|
21726
|
-
import * as
|
|
23762
|
+
import * as fs20 from "fs";
|
|
23763
|
+
import * as path28 from "path";
|
|
23764
|
+
import * as yaml14 from "js-yaml";
|
|
21727
23765
|
function parseArgs6(args2) {
|
|
21728
23766
|
let autoConfirm = false;
|
|
21729
23767
|
let label;
|
|
@@ -21828,14 +23866,14 @@ function printLocalFileChanges(delta) {
|
|
|
21828
23866
|
}
|
|
21829
23867
|
async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, remoteConfig) {
|
|
21830
23868
|
requireRealHubFolder(hubFolder, false);
|
|
21831
|
-
const agentsDir =
|
|
23869
|
+
const agentsDir = path28.join(hubFolder, "agents");
|
|
21832
23870
|
let agentsWithIds = [];
|
|
21833
|
-
if (
|
|
21834
|
-
const yamlFiles =
|
|
23871
|
+
if (fs20.existsSync(agentsDir)) {
|
|
23872
|
+
const yamlFiles = fs20.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
|
|
21835
23873
|
for (const file of yamlFiles) {
|
|
21836
|
-
const content = readRealFileOrThrow(hubFolder,
|
|
23874
|
+
const content = readRealFileOrThrow(hubFolder, path28.join(agentsDir, file)) ?? "";
|
|
21837
23875
|
try {
|
|
21838
|
-
const agent =
|
|
23876
|
+
const agent = yaml14.load(content);
|
|
21839
23877
|
if (agent?.id && agent.name) {
|
|
21840
23878
|
agentsWithIds.push({ id: agent.id, name: agent.name });
|
|
21841
23879
|
}
|
|
@@ -21848,7 +23886,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21848
23886
|
const yamlPath = resolveHubYamlPath(hubFolder);
|
|
21849
23887
|
if (!yamlPath) return;
|
|
21850
23888
|
const yamlContent = readRealFileOrThrow(hubFolder, yamlPath) ?? "";
|
|
21851
|
-
const config =
|
|
23889
|
+
const config = yaml14.load(yamlContent);
|
|
21852
23890
|
agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
|
|
21853
23891
|
}
|
|
21854
23892
|
if (agentsWithIds.length === 0) return;
|
|
@@ -21879,18 +23917,18 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21879
23917
|
}
|
|
21880
23918
|
}
|
|
21881
23919
|
if (renames.length === 0) return;
|
|
21882
|
-
if (!
|
|
21883
|
-
for (const file of
|
|
23920
|
+
if (!fs20.existsSync(agentsDir)) return;
|
|
23921
|
+
for (const file of fs20.readdirSync(agentsDir)) {
|
|
21884
23922
|
if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
|
|
21885
23923
|
console.warn(` Warning: removing orphaned temp file agents/${file}`);
|
|
21886
|
-
|
|
23924
|
+
fs20.unlinkSync(path28.join(agentsDir, file));
|
|
21887
23925
|
}
|
|
21888
23926
|
}
|
|
21889
23927
|
const renameFileIfExists = (dir, oldName, newName) => {
|
|
21890
|
-
const oldPath =
|
|
21891
|
-
const newPath =
|
|
21892
|
-
if (!
|
|
21893
|
-
|
|
23928
|
+
const oldPath = path28.join(dir, oldName);
|
|
23929
|
+
const newPath = path28.join(dir, newName);
|
|
23930
|
+
if (!fs20.existsSync(oldPath)) return false;
|
|
23931
|
+
fs20.renameSync(oldPath, newPath);
|
|
21894
23932
|
return true;
|
|
21895
23933
|
};
|
|
21896
23934
|
const oldSlugs = new Set(renames.map((r) => r.oldSlug));
|
|
@@ -21923,9 +23961,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21923
23961
|
}
|
|
21924
23962
|
} else {
|
|
21925
23963
|
for (const { oldSlug, newSlug } of renames) {
|
|
21926
|
-
const hasOldFile = extensions.some((ext) =>
|
|
23964
|
+
const hasOldFile = extensions.some((ext) => fs20.existsSync(path28.join(agentsDir, `${oldSlug}${ext}`)));
|
|
21927
23965
|
if (!hasOldFile) continue;
|
|
21928
|
-
const hasNewFile = extensions.some((ext) =>
|
|
23966
|
+
const hasNewFile = extensions.some((ext) => fs20.existsSync(path28.join(agentsDir, `${newSlug}${ext}`)));
|
|
21929
23967
|
if (hasNewFile) {
|
|
21930
23968
|
console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
|
|
21931
23969
|
continue;
|
|
@@ -21952,7 +23990,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21952
23990
|
);
|
|
21953
23991
|
if (updatedYaml !== mainYamlContent) {
|
|
21954
23992
|
writeFileNoFollow(hubFolder, mainYamlPath, Buffer.from(updatedYaml, "utf-8"));
|
|
21955
|
-
console.log(` Updated instructions paths in ${
|
|
23993
|
+
console.log(` Updated instructions paths in ${path28.basename(mainYamlPath)}`);
|
|
21956
23994
|
}
|
|
21957
23995
|
}
|
|
21958
23996
|
}
|
|
@@ -22020,7 +24058,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
|
|
|
22020
24058
|
function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
|
|
22021
24059
|
if (selector) {
|
|
22022
24060
|
const match = allHubs.find(
|
|
22023
|
-
(h) => h.hubId === selector ||
|
|
24061
|
+
(h) => h.hubId === selector || path28.basename(h.hubFolder) === selector
|
|
22024
24062
|
);
|
|
22025
24063
|
if (!match) {
|
|
22026
24064
|
console.error(`No hub matching --hub ${selector} found in ${wsLabel}/.`);
|
|
@@ -22089,7 +24127,7 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
|
|
|
22089
24127
|
if (existingHubs.length > 0) {
|
|
22090
24128
|
console.error(`No pushable hub found in ${wsLabel}/ \u2014 only read-only production mirror folder(s). Edit the linked preview hub, pull one, or create a new hub with \`wayai create\`:`);
|
|
22091
24129
|
for (const h of existingHubs) {
|
|
22092
|
-
console.error(` ${
|
|
24130
|
+
console.error(` ${path28.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
|
|
22093
24131
|
}
|
|
22094
24132
|
} else {
|
|
22095
24133
|
console.error(`No hub folders found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` and run \`wayai push\` (or \`wayai create\`).`);
|
|
@@ -22103,10 +24141,10 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
|
|
|
22103
24141
|
console.error(`Multiple hub folders found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
|
|
22104
24142
|
}
|
|
22105
24143
|
for (const h of existingHubs) {
|
|
22106
|
-
console.error(` ${
|
|
24144
|
+
console.error(` ${path28.basename(h.hubFolder)} (${h.hubId})`);
|
|
22107
24145
|
}
|
|
22108
24146
|
for (const h of newHubs) {
|
|
22109
|
-
console.error(` ${
|
|
24147
|
+
console.error(` ${path28.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
|
|
22110
24148
|
}
|
|
22111
24149
|
if (hint) console.error(hint);
|
|
22112
24150
|
process.exit(1);
|
|
@@ -22133,7 +24171,7 @@ async function pushCommand(args2) {
|
|
|
22133
24171
|
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
22134
24172
|
const workspaceDir = resolveWorkspaceDir();
|
|
22135
24173
|
const wsLabel = hubsDirLabel(gitRoot);
|
|
22136
|
-
if (!
|
|
24174
|
+
if (!fs20.existsSync(workspaceDir)) {
|
|
22137
24175
|
console.error(`No ${wsLabel}/ directory found. Run \`wayai pull\` first or create hub files in ${wsLabel}/<hub>/hub.yaml.`);
|
|
22138
24176
|
const hint = communityFoldersHint(gitRoot, "push");
|
|
22139
24177
|
if (hint) console.error(hint);
|
|
@@ -22155,8 +24193,8 @@ async function pushCommand(args2) {
|
|
|
22155
24193
|
if (label) {
|
|
22156
24194
|
console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
|
|
22157
24195
|
}
|
|
22158
|
-
console.log(`Target hub: ${
|
|
22159
|
-
assertInScope("hubs", existing.hubId,
|
|
24196
|
+
console.log(`Target hub: ${path28.basename(existing.hubFolder)} (${existing.hubId})`);
|
|
24197
|
+
assertInScope("hubs", existing.hubId, path28.basename(existing.hubFolder));
|
|
22160
24198
|
await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
|
|
22161
24199
|
return;
|
|
22162
24200
|
}
|
|
@@ -22194,8 +24232,8 @@ __export(pull_exports, {
|
|
|
22194
24232
|
resolveHubTarget: () => resolveHubTarget,
|
|
22195
24233
|
writeProductionMirror: () => writeProductionMirror2
|
|
22196
24234
|
});
|
|
22197
|
-
import * as
|
|
22198
|
-
import * as
|
|
24235
|
+
import * as fs21 from "fs";
|
|
24236
|
+
import * as path29 from "path";
|
|
22199
24237
|
function parseArgs7(args2) {
|
|
22200
24238
|
return { autoConfirm: args2.includes("--yes") || args2.includes("-y") };
|
|
22201
24239
|
}
|
|
@@ -22205,7 +24243,7 @@ function resolveHubTarget(workspaceDir, selector) {
|
|
|
22205
24243
|
const allHubs = scanWorkspaceHubs(workspaceDir);
|
|
22206
24244
|
if (selector) {
|
|
22207
24245
|
const match = allHubs.find(
|
|
22208
|
-
(h) => h.hubId === selector ||
|
|
24246
|
+
(h) => h.hubId === selector || path29.basename(h.hubFolder) === selector
|
|
22209
24247
|
);
|
|
22210
24248
|
if (match) return { hubId: match.hubId, hubFolder: match.hubFolder };
|
|
22211
24249
|
if (UUID_RE3.test(selector)) return { hubId: selector, hubFolder: null };
|
|
@@ -22224,7 +24262,7 @@ function resolveHubTarget(workspaceDir, selector) {
|
|
|
22224
24262
|
}
|
|
22225
24263
|
console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
|
|
22226
24264
|
for (const h of previewHubs) {
|
|
22227
|
-
console.error(` ${
|
|
24265
|
+
console.error(` ${path29.basename(h.hubFolder)} (${h.hubId})`);
|
|
22228
24266
|
}
|
|
22229
24267
|
process.exit(1);
|
|
22230
24268
|
}
|
|
@@ -22273,7 +24311,7 @@ async function pullCommand(args2) {
|
|
|
22273
24311
|
);
|
|
22274
24312
|
let wroteFiles = false;
|
|
22275
24313
|
if (!existingFolder && !resolveHubYamlPath(hubFolder)) {
|
|
22276
|
-
|
|
24314
|
+
fs21.mkdirSync(path29.dirname(hubFolder), { recursive: true });
|
|
22277
24315
|
console.log("Writing hub configuration...");
|
|
22278
24316
|
await materializeHubFolder(hubFolder, payload);
|
|
22279
24317
|
const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
|
|
@@ -22358,7 +24396,7 @@ ${why}`);
|
|
|
22358
24396
|
}
|
|
22359
24397
|
async function writeProductionMirror2(workspaceDir, prodPayload) {
|
|
22360
24398
|
const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
|
|
22361
|
-
|
|
24399
|
+
fs21.mkdirSync(path29.dirname(folder), { recursive: true });
|
|
22362
24400
|
await materializeHubFolder(folder, prodPayload, { seedAgentContext: false });
|
|
22363
24401
|
const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
|
|
22364
24402
|
prependMirrorMarker(finalFolder, prodPayload.hub_id);
|
|
@@ -22374,13 +24412,13 @@ async function mirrorLinkedProduction2(client, workspaceDir, productionHubId, or
|
|
|
22374
24412
|
}
|
|
22375
24413
|
}
|
|
22376
24414
|
function prependMirrorMarker(hubFolder, productionHubId) {
|
|
22377
|
-
const hubYaml =
|
|
24415
|
+
const hubYaml = path29.join(hubFolder, "hub.yaml");
|
|
22378
24416
|
try {
|
|
22379
|
-
const content =
|
|
24417
|
+
const content = fs21.readFileSync(hubYaml, "utf-8");
|
|
22380
24418
|
if (content.startsWith(MIRROR_MARKER_PREFIX2)) return;
|
|
22381
24419
|
const marker = `${MIRROR_MARKER_PREFIX2} ${productionHubId}. Edits are ignored; push is blocked. Edit the linked preview hub instead.
|
|
22382
24420
|
`;
|
|
22383
|
-
|
|
24421
|
+
fs21.writeFileSync(hubYaml, marker + content, "utf-8");
|
|
22384
24422
|
} catch {
|
|
22385
24423
|
}
|
|
22386
24424
|
}
|
|
@@ -22428,8 +24466,8 @@ var create_exports = {};
|
|
|
22428
24466
|
__export(create_exports, {
|
|
22429
24467
|
createCommand: () => createCommand
|
|
22430
24468
|
});
|
|
22431
|
-
import * as
|
|
22432
|
-
import * as
|
|
24469
|
+
import * as path30 from "path";
|
|
24470
|
+
import * as fs22 from "fs";
|
|
22433
24471
|
function parseArgs8(args2) {
|
|
22434
24472
|
let autoConfirm = false;
|
|
22435
24473
|
let folderSelector;
|
|
@@ -22456,7 +24494,7 @@ async function createCommand(args2) {
|
|
|
22456
24494
|
const gitRoot = findGitRoot();
|
|
22457
24495
|
const wsLabel = hubsDirLabel(gitRoot);
|
|
22458
24496
|
if (gitRoot) warnLayoutOnce(gitRoot);
|
|
22459
|
-
if (!
|
|
24497
|
+
if (!fs22.existsSync(workspaceDir)) {
|
|
22460
24498
|
console.error(`No ${wsLabel}/ directory found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` first.`);
|
|
22461
24499
|
process.exit(1);
|
|
22462
24500
|
}
|
|
@@ -22465,7 +24503,7 @@ async function createCommand(args2) {
|
|
|
22465
24503
|
const resolution = resolveNewHubForCreate(workspaceDir, existingHubs, newHubs, folderSelector);
|
|
22466
24504
|
if (!resolution.ok) {
|
|
22467
24505
|
if (resolution.reason === "exists") {
|
|
22468
|
-
const folder =
|
|
24506
|
+
const folder = path30.basename(resolution.existing.hubFolder);
|
|
22469
24507
|
console.error(`Hub "${folder}" already exists (${resolution.existing.hubId}). Use \`wayai push --hub ${folder}\` to update it.`);
|
|
22470
24508
|
process.exit(1);
|
|
22471
24509
|
}
|
|
@@ -22479,7 +24517,7 @@ async function createCommand(args2) {
|
|
|
22479
24517
|
}
|
|
22480
24518
|
console.error(`Multiple new hub folders found in ${wsLabel}/. Pass the folder name (\`wayai create <folder>\`) or run from inside one:`);
|
|
22481
24519
|
for (const h of newHubs) {
|
|
22482
|
-
console.error(` ${
|
|
24520
|
+
console.error(` ${path30.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
|
|
22483
24521
|
}
|
|
22484
24522
|
process.exit(1);
|
|
22485
24523
|
}
|
|
@@ -22590,8 +24628,8 @@ var replicate_exports = {};
|
|
|
22590
24628
|
__export(replicate_exports, {
|
|
22591
24629
|
replicateCommand: () => replicateCommand
|
|
22592
24630
|
});
|
|
22593
|
-
import * as
|
|
22594
|
-
import * as
|
|
24631
|
+
import * as fs23 from "fs";
|
|
24632
|
+
import * as path31 from "path";
|
|
22595
24633
|
function parseArgs10(args2) {
|
|
22596
24634
|
let label;
|
|
22597
24635
|
let hubSelector;
|
|
@@ -22637,8 +24675,8 @@ async function replicateCommand(args2) {
|
|
|
22637
24675
|
payload.preview_label,
|
|
22638
24676
|
payload.branch_name
|
|
22639
24677
|
);
|
|
22640
|
-
const folderPreExisted =
|
|
22641
|
-
|
|
24678
|
+
const folderPreExisted = fs23.existsSync(hubFolder);
|
|
24679
|
+
fs23.mkdirSync(path31.dirname(hubFolder), { recursive: true });
|
|
22642
24680
|
const delta = await materializeHubFolder(hubFolder, payload);
|
|
22643
24681
|
hubFolder = autoRenameHubFolder(
|
|
22644
24682
|
hubFolder,
|
|
@@ -22650,8 +24688,8 @@ async function replicateCommand(args2) {
|
|
|
22650
24688
|
);
|
|
22651
24689
|
seedScopeIfEmpty("hubs", previewHubId);
|
|
22652
24690
|
if (folderPreExisted) printLocalFileChanges(delta);
|
|
22653
|
-
console.log(`Preview written to ${
|
|
22654
|
-
console.log(` Switch to it with: wayai use ${
|
|
24691
|
+
console.log(`Preview written to ${path31.basename(hubFolder)}`);
|
|
24692
|
+
console.log(` Switch to it with: wayai use ${path31.basename(hubFolder)}`);
|
|
22655
24693
|
}
|
|
22656
24694
|
var init_replicate = __esm({
|
|
22657
24695
|
"src/commands/replicate.ts"() {
|
|
@@ -22674,7 +24712,7 @@ var relabel_exports = {};
|
|
|
22674
24712
|
__export(relabel_exports, {
|
|
22675
24713
|
relabelCommand: () => relabelCommand
|
|
22676
24714
|
});
|
|
22677
|
-
import * as
|
|
24715
|
+
import * as path32 from "path";
|
|
22678
24716
|
function parseArgs11(args2) {
|
|
22679
24717
|
let label;
|
|
22680
24718
|
let clear = false;
|
|
@@ -22718,7 +24756,7 @@ async function relabelCommand(args2) {
|
|
|
22718
24756
|
} else {
|
|
22719
24757
|
console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
|
|
22720
24758
|
for (const h of previewHubs) {
|
|
22721
|
-
console.error(` ${
|
|
24759
|
+
console.error(` ${path32.basename(h.hubFolder)} (${h.hubId})`);
|
|
22722
24760
|
}
|
|
22723
24761
|
}
|
|
22724
24762
|
process.exit(1);
|
|
@@ -22727,7 +24765,7 @@ async function relabelCommand(args2) {
|
|
|
22727
24765
|
console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
|
|
22728
24766
|
return;
|
|
22729
24767
|
}
|
|
22730
|
-
assertInScope("hubs", target.hubId,
|
|
24768
|
+
assertInScope("hubs", target.hubId, path32.basename(target.hubFolder));
|
|
22731
24769
|
console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
|
|
22732
24770
|
const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
|
|
22733
24771
|
const row = data[0];
|
|
@@ -22743,7 +24781,7 @@ async function relabelCommand(args2) {
|
|
|
22743
24781
|
);
|
|
22744
24782
|
console.log(serverLabel ? `Preview label set to "${serverLabel}".` : "Preview label cleared.");
|
|
22745
24783
|
if (finalFolder !== target.hubFolder) {
|
|
22746
|
-
console.log(`Hub folder is now ${
|
|
24784
|
+
console.log(`Hub folder is now ${path32.basename(finalFolder)}`);
|
|
22747
24785
|
}
|
|
22748
24786
|
}
|
|
22749
24787
|
var init_relabel = __esm({
|
|
@@ -22769,7 +24807,7 @@ __export(publish_exports, {
|
|
|
22769
24807
|
renderHubDiff: () => renderHubDiff,
|
|
22770
24808
|
runPublish: () => runPublish
|
|
22771
24809
|
});
|
|
22772
|
-
import * as
|
|
24810
|
+
import * as path33 from "path";
|
|
22773
24811
|
function parseArgs12(args2) {
|
|
22774
24812
|
let autoConfirm = false;
|
|
22775
24813
|
let hubSelector;
|
|
@@ -22878,7 +24916,7 @@ async function publishCommand(args2) {
|
|
|
22878
24916
|
return;
|
|
22879
24917
|
}
|
|
22880
24918
|
}
|
|
22881
|
-
assertInScope("hubs", hubId, hubFolder ?
|
|
24919
|
+
assertInScope("hubs", hubId, hubFolder ? path33.basename(hubFolder) : void 0);
|
|
22882
24920
|
await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
|
|
22883
24921
|
}
|
|
22884
24922
|
var STATUS_STYLE, SECTION_TITLES;
|
|
@@ -22926,7 +24964,7 @@ var init_publish = __esm({
|
|
|
22926
24964
|
});
|
|
22927
24965
|
|
|
22928
24966
|
// src/lib/scope-selector.ts
|
|
22929
|
-
import * as
|
|
24967
|
+
import * as path34 from "path";
|
|
22930
24968
|
function findAddFlag(args2) {
|
|
22931
24969
|
return args2.find((arg) => arg === "--add" || arg.startsWith("--add="));
|
|
22932
24970
|
}
|
|
@@ -22983,7 +25021,7 @@ function resolveHubSelectorToId(gitRoot, selector) {
|
|
|
22983
25021
|
const match = findHubByFolderName(workspaceDir, selector);
|
|
22984
25022
|
if (match) return match.hubId;
|
|
22985
25023
|
const newHubs = scanNewHubs(workspaceDir);
|
|
22986
|
-
const pending = newHubs.find((h) =>
|
|
25024
|
+
const pending = newHubs.find((h) => path34.basename(h.hubFolder) === selector);
|
|
22987
25025
|
if (pending) {
|
|
22988
25026
|
throw expected(
|
|
22989
25027
|
`"${selector}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
|
|
@@ -22994,9 +25032,9 @@ Create it first \u2014 the worktree scope picks it up automatically afterward:
|
|
|
22994
25032
|
throw expected(
|
|
22995
25033
|
[
|
|
22996
25034
|
`No hub matching "${selector}" found in ${hubsDirLabel(gitRoot)}/. Pass a UUID or a folder name from:`,
|
|
22997
|
-
...scanWorkspaceHubs(workspaceDir).map((h) => ` ${
|
|
25035
|
+
...scanWorkspaceHubs(workspaceDir).map((h) => ` ${path34.basename(h.hubFolder)} (${h.hubId})`),
|
|
22998
25036
|
...newHubs.map(
|
|
22999
|
-
(h) => ` ${
|
|
25037
|
+
(h) => ` ${path34.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path34.basename(h.hubFolder)}\`)`
|
|
23000
25038
|
)
|
|
23001
25039
|
].join("\n")
|
|
23002
25040
|
);
|
|
@@ -23086,11 +25124,11 @@ __export(migrate_exports, {
|
|
|
23086
25124
|
migrateCommand: () => migrateCommand
|
|
23087
25125
|
});
|
|
23088
25126
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
23089
|
-
import * as
|
|
23090
|
-
import * as
|
|
25127
|
+
import * as fs24 from "fs";
|
|
25128
|
+
import * as path35 from "path";
|
|
23091
25129
|
function isTracked(gitRoot, p) {
|
|
23092
25130
|
try {
|
|
23093
|
-
execFileSync3("git", ["ls-files", "--error-unmatch", "--",
|
|
25131
|
+
execFileSync3("git", ["ls-files", "--error-unmatch", "--", path35.relative(gitRoot, p)], {
|
|
23094
25132
|
cwd: gitRoot,
|
|
23095
25133
|
stdio: ["pipe", "pipe", "pipe"]
|
|
23096
25134
|
});
|
|
@@ -23100,10 +25138,10 @@ function isTracked(gitRoot, p) {
|
|
|
23100
25138
|
}
|
|
23101
25139
|
}
|
|
23102
25140
|
function moveDir(gitRoot, from, to) {
|
|
23103
|
-
|
|
25141
|
+
fs24.mkdirSync(path35.dirname(to), { recursive: true });
|
|
23104
25142
|
if (isTracked(gitRoot, from)) {
|
|
23105
25143
|
try {
|
|
23106
|
-
execFileSync3("git", ["mv",
|
|
25144
|
+
execFileSync3("git", ["mv", path35.relative(gitRoot, from), path35.relative(gitRoot, to)], {
|
|
23107
25145
|
cwd: gitRoot,
|
|
23108
25146
|
stdio: ["pipe", "pipe", "pipe"]
|
|
23109
25147
|
});
|
|
@@ -23111,7 +25149,7 @@ function moveDir(gitRoot, from, to) {
|
|
|
23111
25149
|
} catch {
|
|
23112
25150
|
}
|
|
23113
25151
|
}
|
|
23114
|
-
|
|
25152
|
+
fs24.renameSync(from, to);
|
|
23115
25153
|
return "fs";
|
|
23116
25154
|
}
|
|
23117
25155
|
async function migrateCommand(_args) {
|
|
@@ -23120,12 +25158,12 @@ async function migrateCommand(_args) {
|
|
|
23120
25158
|
console.error("Not inside a git repository.");
|
|
23121
25159
|
process.exit(1);
|
|
23122
25160
|
}
|
|
23123
|
-
const rel = (p) =>
|
|
23124
|
-
const newWs =
|
|
23125
|
-
const legacyWs =
|
|
23126
|
-
const legacyOrg =
|
|
23127
|
-
const newHubs =
|
|
23128
|
-
const newOrg =
|
|
25161
|
+
const rel = (p) => path35.relative(gitRoot, p);
|
|
25162
|
+
const newWs = path35.join(gitRoot, WAYAI_LAYOUT.wsDir);
|
|
25163
|
+
const legacyWs = path35.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
|
|
25164
|
+
const legacyOrg = path35.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
|
|
25165
|
+
const newHubs = path35.join(newWs, WAYAI_LAYOUT.hubsSubdir);
|
|
25166
|
+
const newOrg = path35.join(newWs, WAYAI_LAYOUT.orgSubdir);
|
|
23129
25167
|
requireRealSubdirNoSymlink(gitRoot, newHubs, false);
|
|
23130
25168
|
requireRealSubdirNoSymlink(gitRoot, newOrg, false);
|
|
23131
25169
|
readRealFileOrThrow(gitRoot, workspaceManifestPath(gitRoot));
|
|
@@ -23278,12 +25316,12 @@ var send_message_exports = {};
|
|
|
23278
25316
|
__export(send_message_exports, {
|
|
23279
25317
|
sendMessageCommand: () => sendMessageCommand
|
|
23280
25318
|
});
|
|
23281
|
-
import * as
|
|
23282
|
-
import * as
|
|
25319
|
+
import * as fs25 from "fs";
|
|
25320
|
+
import * as path36 from "path";
|
|
23283
25321
|
function statAttachment(filePath) {
|
|
23284
25322
|
let stat2;
|
|
23285
25323
|
try {
|
|
23286
|
-
stat2 =
|
|
25324
|
+
stat2 = fs25.statSync(filePath);
|
|
23287
25325
|
} catch {
|
|
23288
25326
|
console.error(`Error: file not found: ${filePath}`);
|
|
23289
25327
|
process.exit(1);
|
|
@@ -23295,11 +25333,11 @@ function statAttachment(filePath) {
|
|
|
23295
25333
|
return { filePath, size: stat2.size };
|
|
23296
25334
|
}
|
|
23297
25335
|
function readAttachment(filePath, size) {
|
|
23298
|
-
const fileName =
|
|
23299
|
-
const ext =
|
|
25336
|
+
const fileName = path36.basename(filePath);
|
|
25337
|
+
const ext = path36.extname(fileName).replace(/^\./, "");
|
|
23300
25338
|
return {
|
|
23301
25339
|
file_name: fileName,
|
|
23302
|
-
file_binary:
|
|
25340
|
+
file_binary: fs25.readFileSync(filePath).toString("base64"),
|
|
23303
25341
|
file_size: size,
|
|
23304
25342
|
...ext && { file_extension: ext }
|
|
23305
25343
|
};
|
|
@@ -23346,7 +25384,7 @@ async function sendMessageCommand(args2) {
|
|
|
23346
25384
|
}
|
|
23347
25385
|
const stats = filePaths.map(statAttachment);
|
|
23348
25386
|
const filesTotal = stats.reduce((sum, s) => sum + base64Length(s.size), 0);
|
|
23349
|
-
const fileDetail = () => stats.map((s) => `${
|
|
25387
|
+
const fileDetail = () => stats.map((s) => `${path36.basename(s.filePath)} ${asMb(s.size)} MB`).join(", ");
|
|
23350
25388
|
if (filesTotal > MAX_MESSAGE_BODY_BYTES) {
|
|
23351
25389
|
console.error(
|
|
23352
25390
|
`Error: ${fileDetail()} encode to ~${asMb(filesTotal)} MB, over the ${asMb(MAX_MESSAGE_BODY_BYTES)} MB per-request limit.`
|
|
@@ -23354,10 +25392,10 @@ async function sendMessageCommand(args2) {
|
|
|
23354
25392
|
console.error("Base64 inflates a file by about a third, so ~7 MB of files is the practical ceiling.");
|
|
23355
25393
|
process.exit(1);
|
|
23356
25394
|
}
|
|
23357
|
-
const audioStats = stats.filter((s) => isAudioAttachmentFileName(
|
|
25395
|
+
const audioStats = stats.filter((s) => isAudioAttachmentFileName(path36.basename(s.filePath)));
|
|
23358
25396
|
if (audioStats.length > 1) {
|
|
23359
25397
|
console.error(
|
|
23360
|
-
`Error: at most one audio file per message (got ${audioStats.length}: ${audioStats.map((s) =>
|
|
25398
|
+
`Error: at most one audio file per message (got ${audioStats.length}: ${audioStats.map((s) => path36.basename(s.filePath)).join(", ")}).`
|
|
23361
25399
|
);
|
|
23362
25400
|
console.error("Audio is transcribed via the hub's STT connection, and only one file per message is transcribed.");
|
|
23363
25401
|
process.exit(1);
|
|
@@ -23365,7 +25403,7 @@ async function sendMessageCommand(args2) {
|
|
|
23365
25403
|
const oversizeAudio = audioStats.find((s) => base64Length(s.size) > MAX_AUDIO_FILE_BASE64_BYTES);
|
|
23366
25404
|
if (oversizeAudio) {
|
|
23367
25405
|
console.error(
|
|
23368
|
-
`Error: ${
|
|
25406
|
+
`Error: ${path36.basename(oversizeAudio.filePath)} (${asMb(oversizeAudio.size)} MB) is over the ~${asMb(MAX_AUDIO_FILE_BASE64_BYTES * 3 / 4)} MB audio limit for transcription.`
|
|
23369
25407
|
);
|
|
23370
25408
|
process.exit(1);
|
|
23371
25409
|
}
|
|
@@ -24841,133 +26879,6 @@ var init_evals = __esm({
|
|
|
24841
26879
|
}
|
|
24842
26880
|
});
|
|
24843
26881
|
|
|
24844
|
-
// src/lib/eval-session-control.ts
|
|
24845
|
-
function formatEvalSessionStopCommand(sessionId, hubId) {
|
|
24846
|
-
return `wayai eval session stop ${sessionId} --hub ${hubId}`;
|
|
24847
|
-
}
|
|
24848
|
-
function formatEvalResultsCommand(sessionId, hubId) {
|
|
24849
|
-
return `wayai eval-results --session ${sessionId} --hub ${hubId}`;
|
|
24850
|
-
}
|
|
24851
|
-
function describeSeedRelease(status) {
|
|
24852
|
-
if (status === "pending") {
|
|
24853
|
-
return "A turn that started before the stop is still finishing \u2014 its fixture stays reserved until that turn settles.";
|
|
24854
|
-
}
|
|
24855
|
-
if (status === "releasing") return "Its fixture is being released.";
|
|
24856
|
-
return "A turn already in progress may still be finishing \u2014 wait for it before starting another run.";
|
|
24857
|
-
}
|
|
24858
|
-
function classifyStopOutcome(response) {
|
|
24859
|
-
return response.data.stopped ? { kind: "cancelled", seedRelease: response.data.seed_release_status } : { kind: "already_finished", status: response.data.session.session_status };
|
|
24860
|
-
}
|
|
24861
|
-
function isTerminalStopFailure(err) {
|
|
24862
|
-
return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 408 && err.status !== 429;
|
|
24863
|
-
}
|
|
24864
|
-
async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
24865
|
-
const attempts = opts.attempts ?? STOP_ATTEMPTS;
|
|
24866
|
-
const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
|
|
24867
|
-
const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
|
|
24868
|
-
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
24869
|
-
let lastError = new Error("Stop was never attempted");
|
|
24870
|
-
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
24871
|
-
let timeout;
|
|
24872
|
-
try {
|
|
24873
|
-
return await Promise.race([
|
|
24874
|
-
stop(),
|
|
24875
|
-
new Promise((_resolve, reject) => {
|
|
24876
|
-
timeout = setTimeout(
|
|
24877
|
-
() => reject(new Error(`Stop attempt ${attempt} timed out after ${attemptTimeoutMs}ms`)),
|
|
24878
|
-
attemptTimeoutMs
|
|
24879
|
-
);
|
|
24880
|
-
})
|
|
24881
|
-
]);
|
|
24882
|
-
} catch (err) {
|
|
24883
|
-
lastError = err;
|
|
24884
|
-
if (isTerminalStopFailure(err)) break;
|
|
24885
|
-
} finally {
|
|
24886
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
24887
|
-
}
|
|
24888
|
-
const delay2 = backoffMs[attempt - 1];
|
|
24889
|
-
if (attempt < attempts && delay2) await sleep3(delay2);
|
|
24890
|
-
}
|
|
24891
|
-
throw lastError;
|
|
24892
|
-
}
|
|
24893
|
-
function installEvalSignalHandlers(input) {
|
|
24894
|
-
const exit = input.exit ?? ((code) => process.exit(code));
|
|
24895
|
-
const manualStopCommand = formatEvalSessionStopCommand(input.sessionId, input.hubId);
|
|
24896
|
-
let signalCount = 0;
|
|
24897
|
-
let disposed = false;
|
|
24898
|
-
let settle;
|
|
24899
|
-
const settled = new Promise((resolve12) => {
|
|
24900
|
-
settle = resolve12;
|
|
24901
|
-
});
|
|
24902
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
24903
|
-
const dispose = () => {
|
|
24904
|
-
if (disposed) return;
|
|
24905
|
-
disposed = true;
|
|
24906
|
-
for (const [signal, listener] of listeners) process.off(signal, listener);
|
|
24907
|
-
};
|
|
24908
|
-
const finish = (exitCode) => {
|
|
24909
|
-
dispose();
|
|
24910
|
-
settle();
|
|
24911
|
-
exit(exitCode);
|
|
24912
|
-
};
|
|
24913
|
-
const handle = (signal) => {
|
|
24914
|
-
signalCount += 1;
|
|
24915
|
-
const signalExitCode = SIGNAL_EXIT_CODE[signal];
|
|
24916
|
-
if (signalCount > 1) {
|
|
24917
|
-
console.error("\nSecond signal received; exiting immediately. The remote eval session may still be running.");
|
|
24918
|
-
console.error(`Run: ${manualStopCommand}`);
|
|
24919
|
-
finish(signalExitCode);
|
|
24920
|
-
return;
|
|
24921
|
-
}
|
|
24922
|
-
console.error(`
|
|
24923
|
-
${signal} received; stopping remote eval session ${input.sessionId}...`);
|
|
24924
|
-
void stopEvalSessionWithRetries(input.stop).then((response) => {
|
|
24925
|
-
if (signalCount > 1) return;
|
|
24926
|
-
const outcome = classifyStopOutcome(response);
|
|
24927
|
-
if (outcome.kind === "already_finished") {
|
|
24928
|
-
console.error(`Session had already finished (${outcome.status}) \u2014 nothing was cancelled.`);
|
|
24929
|
-
console.error(`See its results: ${formatEvalResultsCommand(input.sessionId, input.hubId)}`);
|
|
24930
|
-
} else {
|
|
24931
|
-
console.error("Remote session cancellation requested.");
|
|
24932
|
-
console.error(describeSeedRelease(outcome.seedRelease));
|
|
24933
|
-
}
|
|
24934
|
-
finish(signalExitCode);
|
|
24935
|
-
}).catch((err) => {
|
|
24936
|
-
if (signalCount > 1) return;
|
|
24937
|
-
console.error(`Failed to confirm remote cancellation: ${extractApiMessage(err)}`);
|
|
24938
|
-
console.error(`The session may still be running. Run: ${manualStopCommand}`);
|
|
24939
|
-
finish(1);
|
|
24940
|
-
});
|
|
24941
|
-
};
|
|
24942
|
-
for (const signal of HANDLED_SIGNALS) {
|
|
24943
|
-
const listener = () => handle(signal);
|
|
24944
|
-
listeners.set(signal, listener);
|
|
24945
|
-
process.on(signal, listener);
|
|
24946
|
-
}
|
|
24947
|
-
return {
|
|
24948
|
-
manualStopCommand,
|
|
24949
|
-
async interrupted() {
|
|
24950
|
-
if (signalCount === 0) return false;
|
|
24951
|
-
await settled;
|
|
24952
|
-
return true;
|
|
24953
|
-
},
|
|
24954
|
-
dispose
|
|
24955
|
-
};
|
|
24956
|
-
}
|
|
24957
|
-
var SIGNAL_EXIT_CODE, HANDLED_SIGNALS, STOP_ATTEMPTS, STOP_ATTEMPT_TIMEOUT_MS, STOP_BACKOFF_MS;
|
|
24958
|
-
var init_eval_session_control = __esm({
|
|
24959
|
-
"src/lib/eval-session-control.ts"() {
|
|
24960
|
-
"use strict";
|
|
24961
|
-
init_api_client();
|
|
24962
|
-
init_errors2();
|
|
24963
|
-
SIGNAL_EXIT_CODE = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
|
|
24964
|
-
HANDLED_SIGNALS = Object.keys(SIGNAL_EXIT_CODE);
|
|
24965
|
-
STOP_ATTEMPTS = 3;
|
|
24966
|
-
STOP_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
24967
|
-
STOP_BACKOFF_MS = [500, 1e3];
|
|
24968
|
-
}
|
|
24969
|
-
});
|
|
24970
|
-
|
|
24971
26882
|
// src/lib/eval-fixture-queue.ts
|
|
24972
26883
|
function isQueueableLaunchError(err) {
|
|
24973
26884
|
const code = extractApiErrorCode(err);
|
|
@@ -25520,7 +27431,7 @@ var init_eval_results = __esm({
|
|
|
25520
27431
|
import { createHash as createHash4 } from "crypto";
|
|
25521
27432
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync17, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
25522
27433
|
import { homedir as homedir5 } from "os";
|
|
25523
|
-
import { dirname as
|
|
27434
|
+
import { dirname as dirname12, isAbsolute as isAbsolute2, join as join31, resolve as resolve10 } from "path";
|
|
25524
27435
|
function parseWav(buf, name = "clip") {
|
|
25525
27436
|
if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
25526
27437
|
throw new Error(`${name} is not a WAV file.`);
|
|
@@ -25559,7 +27470,7 @@ function parseWav(buf, name = "clip") {
|
|
|
25559
27470
|
throw new Error(`${name} holds no audio data.`);
|
|
25560
27471
|
}
|
|
25561
27472
|
function defaultClipCacheDir() {
|
|
25562
|
-
return
|
|
27473
|
+
return join31(homedir5(), ".wayai", "call-clips");
|
|
25563
27474
|
}
|
|
25564
27475
|
function pcmClip(buf) {
|
|
25565
27476
|
const samples = new Int16Array(Math.floor(buf.length / 2));
|
|
@@ -25608,15 +27519,15 @@ var init_caller_audio = __esm({
|
|
|
25608
27519
|
}
|
|
25609
27520
|
describe;
|
|
25610
27521
|
async clipFor(line) {
|
|
25611
|
-
const
|
|
25612
|
-
if (!
|
|
27522
|
+
const path41 = line.clip ? isAbsolute2(line.clip) ? line.clip : resolve10(this.planDir, line.clip) : this.dir ? join31(this.dir, `${line.n}.wav`) : null;
|
|
27523
|
+
if (!path41) throw new Error(`Line ${line.n} has no clip: give --clips <dir>, or a "clip" for it in the plan.`);
|
|
25613
27524
|
let buf;
|
|
25614
27525
|
try {
|
|
25615
|
-
buf = readFileSync17(
|
|
27526
|
+
buf = readFileSync17(path41);
|
|
25616
27527
|
} catch {
|
|
25617
|
-
throw new Error(`Line ${line.n}'s clip ${
|
|
27528
|
+
throw new Error(`Line ${line.n}'s clip ${path41} cannot be read.`);
|
|
25618
27529
|
}
|
|
25619
|
-
return parseWav(buf,
|
|
27530
|
+
return parseWav(buf, path41);
|
|
25620
27531
|
}
|
|
25621
27532
|
};
|
|
25622
27533
|
OPENAI_PCM_SAMPLE_RATE = 24e3;
|
|
@@ -25643,20 +27554,20 @@ var init_caller_audio = __esm({
|
|
|
25643
27554
|
const key = createHash4("sha256").update(`${this.model}
|
|
25644
27555
|
${this.voice}
|
|
25645
27556
|
${text}`).digest("hex");
|
|
25646
|
-
return
|
|
27557
|
+
return join31(this.cacheDir, `${key}.pcm`);
|
|
25647
27558
|
}
|
|
25648
27559
|
clipFor(line) {
|
|
25649
|
-
const
|
|
25650
|
-
let pending = this.inFlight.get(
|
|
27560
|
+
const path41 = this.cachePath(line.say);
|
|
27561
|
+
let pending = this.inFlight.get(path41);
|
|
25651
27562
|
if (!pending) {
|
|
25652
|
-
pending = this.readOrSynthesize(line,
|
|
25653
|
-
this.inFlight.set(
|
|
27563
|
+
pending = this.readOrSynthesize(line, path41);
|
|
27564
|
+
this.inFlight.set(path41, pending);
|
|
25654
27565
|
}
|
|
25655
27566
|
return pending;
|
|
25656
27567
|
}
|
|
25657
|
-
async readOrSynthesize(line,
|
|
27568
|
+
async readOrSynthesize(line, path41) {
|
|
25658
27569
|
try {
|
|
25659
|
-
return pcmClip(readFileSync17(
|
|
27570
|
+
return pcmClip(readFileSync17(path41));
|
|
25660
27571
|
} catch {
|
|
25661
27572
|
}
|
|
25662
27573
|
const res = await this.doFetch("https://api.openai.com/v1/audio/speech", {
|
|
@@ -25670,10 +27581,10 @@ ${text}`).digest("hex");
|
|
|
25670
27581
|
}
|
|
25671
27582
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
25672
27583
|
if (buf.length < 2) throw new Error(`OpenAI speech synthesis returned no audio for line ${line.n}.`);
|
|
25673
|
-
mkdirSync12(
|
|
25674
|
-
const tmp = `${
|
|
27584
|
+
mkdirSync12(dirname12(path41), { recursive: true });
|
|
27585
|
+
const tmp = `${path41}.${process.pid}.tmp`;
|
|
25675
27586
|
writeFileSync7(tmp, buf);
|
|
25676
|
-
renameSync5(tmp,
|
|
27587
|
+
renameSync5(tmp, path41);
|
|
25677
27588
|
return pcmClip(buf);
|
|
25678
27589
|
}
|
|
25679
27590
|
};
|
|
@@ -27163,7 +29074,7 @@ __export(eval_call_exports, {
|
|
|
27163
29074
|
suiteExitCode: () => suiteExitCode
|
|
27164
29075
|
});
|
|
27165
29076
|
import { accessSync, constants as fsConstants, existsSync as existsSync16, readFileSync as readFileSync18, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
27166
|
-
import { dirname as
|
|
29077
|
+
import { dirname as dirname13, resolve as resolve11 } from "path";
|
|
27167
29078
|
function fail(message) {
|
|
27168
29079
|
throw new Error(message);
|
|
27169
29080
|
}
|
|
@@ -27238,15 +29149,15 @@ function parseEvalCallArgs(args2) {
|
|
|
27238
29149
|
if (parsed.jsonPath) checkReportPath(parsed.jsonPath);
|
|
27239
29150
|
return parsed;
|
|
27240
29151
|
}
|
|
27241
|
-
function checkReportPath(
|
|
27242
|
-
const target = resolve11(
|
|
27243
|
-
const dir =
|
|
27244
|
-
if (!existsSync16(dir) || !statSync8(dir).isDirectory()) fail(`--json ${
|
|
27245
|
-
if (existsSync16(target) && statSync8(target).isDirectory()) fail(`--json ${
|
|
29152
|
+
function checkReportPath(path41) {
|
|
29153
|
+
const target = resolve11(path41);
|
|
29154
|
+
const dir = dirname13(target);
|
|
29155
|
+
if (!existsSync16(dir) || !statSync8(dir).isDirectory()) fail(`--json ${path41}: the folder ${dir} does not exist.`);
|
|
29156
|
+
if (existsSync16(target) && statSync8(target).isDirectory()) fail(`--json ${path41} is a folder; name a file.`);
|
|
27246
29157
|
try {
|
|
27247
29158
|
accessSync(existsSync16(target) ? target : dir, fsConstants.W_OK);
|
|
27248
29159
|
} catch {
|
|
27249
|
-
fail(`--json ${
|
|
29160
|
+
fail(`--json ${path41} cannot be written here.`);
|
|
27250
29161
|
}
|
|
27251
29162
|
}
|
|
27252
29163
|
function suiteExitCode(report2, runs, reportWritten) {
|
|
@@ -27257,7 +29168,7 @@ function formatCallPages(runs) {
|
|
|
27257
29168
|
return pages.length > 0 ? ["Call pages (the transcript, and the recording once it is ready):", ...pages] : [];
|
|
27258
29169
|
}
|
|
27259
29170
|
function chooseAudioSource(options, plan, env = process.env) {
|
|
27260
|
-
const planDir = options.planPath ?
|
|
29171
|
+
const planDir = options.planPath ? dirname13(resolve11(options.planPath)) : process.cwd();
|
|
27261
29172
|
const planHasClips = Object.values(plan.lines).some((line) => line.clip);
|
|
27262
29173
|
if (options.clipsDir || planHasClips) {
|
|
27263
29174
|
if (options.clipsDir && !existsSync16(options.clipsDir)) fail(`--clips ${options.clipsDir} does not exist.`);
|
|
@@ -27456,9 +29367,9 @@ var eval_capture_exports = {};
|
|
|
27456
29367
|
__export(eval_capture_exports, {
|
|
27457
29368
|
evalCaptureCommand: () => evalCaptureCommand
|
|
27458
29369
|
});
|
|
27459
|
-
import * as
|
|
27460
|
-
import * as
|
|
27461
|
-
import * as
|
|
29370
|
+
import * as fs26 from "fs";
|
|
29371
|
+
import * as path37 from "path";
|
|
29372
|
+
import * as yaml15 from "js-yaml";
|
|
27462
29373
|
function isValidSetName(name) {
|
|
27463
29374
|
if (name.length === 0 || name === "." || name === "..") return false;
|
|
27464
29375
|
if (name.startsWith(".")) return false;
|
|
@@ -27523,25 +29434,25 @@ async function evalCaptureCommand(args2) {
|
|
|
27523
29434
|
const setFolderName = targetSetName;
|
|
27524
29435
|
const scenarioName = parsed.evalName ?? `Capture ${parsed.conversationId.slice(0, 8)}`;
|
|
27525
29436
|
const slug = slugify2(scenarioName);
|
|
27526
|
-
const evalsDir =
|
|
27527
|
-
const targetDir =
|
|
27528
|
-
const targetPath =
|
|
27529
|
-
if (!targetPath.startsWith(evalsDir +
|
|
27530
|
-
console.error(`Resolved path "${
|
|
29437
|
+
const evalsDir = path37.join(hubFolder, "evals");
|
|
29438
|
+
const targetDir = path37.join(evalsDir, setFolderName);
|
|
29439
|
+
const targetPath = path37.join(targetDir, `${slug}.yaml`);
|
|
29440
|
+
if (!targetPath.startsWith(evalsDir + path37.sep)) {
|
|
29441
|
+
console.error(`Resolved path "${path37.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
|
|
27531
29442
|
process.exit(1);
|
|
27532
29443
|
}
|
|
27533
29444
|
if (!ensureRealSubdirNoSymlink(hubFolder, targetDir, false)) {
|
|
27534
|
-
console.error(`${
|
|
29445
|
+
console.error(`${path37.relative(hubFolder, targetDir)} is reached through a symlink. Aborting.`);
|
|
27535
29446
|
process.exit(1);
|
|
27536
29447
|
}
|
|
27537
29448
|
let targetTaken = true;
|
|
27538
29449
|
try {
|
|
27539
|
-
|
|
29450
|
+
fs26.lstatSync(targetPath);
|
|
27540
29451
|
} catch {
|
|
27541
29452
|
targetTaken = false;
|
|
27542
29453
|
}
|
|
27543
29454
|
if (targetTaken) {
|
|
27544
|
-
console.error(`File already exists: ${
|
|
29455
|
+
console.error(`File already exists: ${path37.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
|
|
27545
29456
|
process.exit(1);
|
|
27546
29457
|
}
|
|
27547
29458
|
console.log("Resolving scenario set...");
|
|
@@ -27573,14 +29484,14 @@ async function evalCaptureCommand(args2) {
|
|
|
27573
29484
|
...captured.evaluator_instructions ? { evaluator_instructions: captured.evaluator_instructions } : {}
|
|
27574
29485
|
};
|
|
27575
29486
|
const yamlObj = buildEvalYamlObject(evalEntry, slug);
|
|
27576
|
-
const outcome = createFileNoFollow(hubFolder, targetPath, Buffer.from(
|
|
29487
|
+
const outcome = createFileNoFollow(hubFolder, targetPath, Buffer.from(yaml15.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8"));
|
|
27577
29488
|
if (outcome !== "created") {
|
|
27578
29489
|
console.error(
|
|
27579
|
-
`Could not write ${
|
|
29490
|
+
`Could not write ${path37.relative(hubFolder, targetPath)} (${outcome === "exists" ? "it appeared meanwhile" : "its folder is reached through a symlink"}). The scenario was created on the platform as ${captured.eval_id} \u2014 run \`wayai pull\` to fetch it.`
|
|
27580
29491
|
);
|
|
27581
29492
|
process.exit(1);
|
|
27582
29493
|
}
|
|
27583
|
-
const relPath =
|
|
29494
|
+
const relPath = path37.relative(process.cwd(), targetPath);
|
|
27584
29495
|
console.log(`
|
|
27585
29496
|
Wrote ${relPath}`);
|
|
27586
29497
|
console.log("Run `wayai pull` to refresh the agent display name, then commit. The scenario is already on the platform.");
|
|
@@ -28960,20 +30871,20 @@ var init_set_connection_credential = __esm({
|
|
|
28960
30871
|
});
|
|
28961
30872
|
|
|
28962
30873
|
// src/lib/org-workspace.ts
|
|
28963
|
-
import * as
|
|
28964
|
-
import * as
|
|
28965
|
-
import * as
|
|
30874
|
+
import * as fs27 from "fs";
|
|
30875
|
+
import * as path38 from "path";
|
|
30876
|
+
import * as yaml16 from "js-yaml";
|
|
28966
30877
|
function getOrgDir(gitRoot) {
|
|
28967
30878
|
return resolveLayout(gitRoot).orgDir;
|
|
28968
30879
|
}
|
|
28969
30880
|
function orgManifestExists(orgDir) {
|
|
28970
|
-
return
|
|
30881
|
+
return fs27.existsSync(path38.join(orgDir, ORG_MANIFEST_NAME));
|
|
28971
30882
|
}
|
|
28972
30883
|
function parseOrgResources(orgDir) {
|
|
28973
|
-
const resourcesDir =
|
|
30884
|
+
const resourcesDir = path38.join(orgDir, "resources");
|
|
28974
30885
|
requireRealSubdirNoSymlink(orgDir, resourcesDir, false);
|
|
28975
|
-
const manifestText = readRealFileOrThrow(orgDir,
|
|
28976
|
-
const manifest = (manifestText !== null ?
|
|
30886
|
+
const manifestText = readRealFileOrThrow(orgDir, path38.join(orgDir, ORG_MANIFEST_NAME));
|
|
30887
|
+
const manifest = (manifestText !== null ? yaml16.load(manifestText) : null) ?? {};
|
|
28977
30888
|
const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
|
|
28978
30889
|
const resources = rawResources.map((res) => {
|
|
28979
30890
|
const resource = { name: res.name };
|
|
@@ -28985,9 +30896,9 @@ function parseOrgResources(orgDir) {
|
|
|
28985
30896
|
if (res.environment) resource.environment = res.environment;
|
|
28986
30897
|
if (Array.isArray(res.tags)) resource.tags = res.tags;
|
|
28987
30898
|
if (Array.isArray(res.folders)) resource.folders = res.folders;
|
|
28988
|
-
const resDir =
|
|
30899
|
+
const resDir = path38.join(resourcesDir, slugify2(resource.name));
|
|
28989
30900
|
requireRealSubdirNoSymlink(orgDir, resDir, false);
|
|
28990
|
-
if (
|
|
30901
|
+
if (fs27.existsSync(resDir)) {
|
|
28991
30902
|
const files = scanResourceFiles(resDir, "");
|
|
28992
30903
|
if (files.length > 0) resource.files = files;
|
|
28993
30904
|
}
|
|
@@ -28996,8 +30907,8 @@ function parseOrgResources(orgDir) {
|
|
|
28996
30907
|
return { version: 1, resources };
|
|
28997
30908
|
}
|
|
28998
30909
|
function writeOrgResources(orgDir, payload) {
|
|
28999
|
-
|
|
29000
|
-
const resourcesDir =
|
|
30910
|
+
fs27.mkdirSync(orgDir, { recursive: true });
|
|
30911
|
+
const resourcesDir = path38.join(orgDir, "resources");
|
|
29001
30912
|
requireRealSubdirNoSymlink(orgDir, resourcesDir, false);
|
|
29002
30913
|
const resources = payload.resources ?? [];
|
|
29003
30914
|
const manifestResources = resources.map((r) => {
|
|
@@ -29006,19 +30917,19 @@ function writeOrgResources(orgDir, payload) {
|
|
|
29006
30917
|
});
|
|
29007
30918
|
writeFileNoFollow(
|
|
29008
30919
|
orgDir,
|
|
29009
|
-
|
|
29010
|
-
Buffer.from(
|
|
30920
|
+
path38.join(orgDir, ORG_MANIFEST_NAME),
|
|
30921
|
+
Buffer.from(yaml16.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS), "utf-8")
|
|
29011
30922
|
);
|
|
29012
30923
|
const currentSlugs = /* @__PURE__ */ new Set();
|
|
29013
30924
|
for (const resource of resources) {
|
|
29014
30925
|
const resSlug = slugify2(resource.name);
|
|
29015
30926
|
currentSlugs.add(resSlug);
|
|
29016
|
-
writeResourceFileTree(
|
|
30927
|
+
writeResourceFileTree(path38.join(resourcesDir, resSlug), resource.files || [], orgDir);
|
|
29017
30928
|
}
|
|
29018
|
-
if (
|
|
29019
|
-
for (const entry of
|
|
30929
|
+
if (fs27.existsSync(resourcesDir)) {
|
|
30930
|
+
for (const entry of fs27.readdirSync(resourcesDir, { withFileTypes: true })) {
|
|
29020
30931
|
if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
|
|
29021
|
-
|
|
30932
|
+
fs27.rmSync(path38.join(resourcesDir, entry.name), { recursive: true, force: true });
|
|
29022
30933
|
}
|
|
29023
30934
|
}
|
|
29024
30935
|
}
|
|
@@ -29027,7 +30938,7 @@ async function downloadOrgBinaryFiles(orgDir, payload) {
|
|
|
29027
30938
|
let count = 0;
|
|
29028
30939
|
for (const resource of payload.resources ?? []) {
|
|
29029
30940
|
if (!resource.files) continue;
|
|
29030
|
-
const resDir =
|
|
30941
|
+
const resDir = path38.join(orgDir, "resources", slugify2(resource.name));
|
|
29031
30942
|
count += await downloadBinaryFiles(resDir, resource.files, orgDir);
|
|
29032
30943
|
}
|
|
29033
30944
|
if (count > 0) console.log(`Downloaded ${count} binary resource file(s).`);
|
|
@@ -29650,8 +31561,8 @@ var init_report_edit_args = __esm({
|
|
|
29650
31561
|
});
|
|
29651
31562
|
|
|
29652
31563
|
// src/lib/file-map.ts
|
|
29653
|
-
import * as
|
|
29654
|
-
import * as
|
|
31564
|
+
import * as fs28 from "fs";
|
|
31565
|
+
import * as path39 from "path";
|
|
29655
31566
|
function isSafeRelPath(rel) {
|
|
29656
31567
|
if (rel.length === 0 || rel.length > 300) return false;
|
|
29657
31568
|
if (rel.startsWith("/") || rel.includes("\\")) return false;
|
|
@@ -29666,9 +31577,9 @@ function writeFileMap(targetDir, files) {
|
|
|
29666
31577
|
if (!isSafeRelPath(rel)) {
|
|
29667
31578
|
throw new Error(`Refusing to write unsafe path: ${rel}`);
|
|
29668
31579
|
}
|
|
29669
|
-
const abs =
|
|
29670
|
-
|
|
29671
|
-
|
|
31580
|
+
const abs = path39.join(targetDir, rel);
|
|
31581
|
+
fs28.mkdirSync(path39.dirname(abs), { recursive: true });
|
|
31582
|
+
fs28.writeFileSync(abs, body, "utf-8");
|
|
29672
31583
|
written.push(rel);
|
|
29673
31584
|
}
|
|
29674
31585
|
return written;
|
|
@@ -29684,8 +31595,8 @@ var admin_exports = {};
|
|
|
29684
31595
|
__export(admin_exports, {
|
|
29685
31596
|
adminCommand: () => adminCommand
|
|
29686
31597
|
});
|
|
29687
|
-
import * as
|
|
29688
|
-
import * as
|
|
31598
|
+
import * as fs29 from "fs";
|
|
31599
|
+
import * as path40 from "path";
|
|
29689
31600
|
async function adminCommand(args2) {
|
|
29690
31601
|
const [group, ...afterGroup] = args2;
|
|
29691
31602
|
if (!group) {
|
|
@@ -30124,7 +32035,7 @@ async function runArchiveRead(positional, flagArgs) {
|
|
|
30124
32035
|
exitOnApiError(err);
|
|
30125
32036
|
throw err;
|
|
30126
32037
|
}
|
|
30127
|
-
|
|
32038
|
+
fs29.writeFileSync(outPath, zip);
|
|
30128
32039
|
console.log(`Wrote ${zip.byteLength} bytes to ${outPath}`);
|
|
30129
32040
|
return;
|
|
30130
32041
|
}
|
|
@@ -30266,13 +32177,13 @@ async function runSkillInstall(positional) {
|
|
|
30266
32177
|
throw err;
|
|
30267
32178
|
}
|
|
30268
32179
|
const root = findGitRoot() ?? process.cwd();
|
|
30269
|
-
const present = HARNESS_SKILL_DIRS.filter((dir) =>
|
|
32180
|
+
const present = HARNESS_SKILL_DIRS.filter((dir) => fs29.existsSync(path40.join(root, dir)));
|
|
30270
32181
|
const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
|
|
30271
32182
|
const fileCount = Object.keys(res.files).length;
|
|
30272
32183
|
const relDirs = targets.map((harness) => {
|
|
30273
|
-
const targetDir =
|
|
32184
|
+
const targetDir = path40.join(root, harness, "skills", name);
|
|
30274
32185
|
writeFileMap(targetDir, res.files);
|
|
30275
|
-
return `${
|
|
32186
|
+
return `${path40.relative(root, targetDir)}/`;
|
|
30276
32187
|
});
|
|
30277
32188
|
console.log(`Installed skill "${name}" (${fileCount} file${fileCount === 1 ? "" : "s"}) \u2192 ${relDirs.join(", ")}`);
|
|
30278
32189
|
console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
|
|
@@ -32865,12 +34776,12 @@ var init_providers = __esm({
|
|
|
32865
34776
|
// src/data/commands/report.ts
|
|
32866
34777
|
import { Command as Command6 } from "commander";
|
|
32867
34778
|
import { readFileSync as readFileSync20 } from "fs";
|
|
32868
|
-
import { dirname as
|
|
34779
|
+
import { dirname as dirname15, join as join36 } from "path";
|
|
32869
34780
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
32870
34781
|
function resolveCliVersion() {
|
|
32871
34782
|
for (const candidate of [
|
|
32872
|
-
|
|
32873
|
-
|
|
34783
|
+
join36(here, "..", "package.json"),
|
|
34784
|
+
join36(here, "..", "..", "..", "package.json")
|
|
32874
34785
|
]) {
|
|
32875
34786
|
try {
|
|
32876
34787
|
const version = JSON.parse(readFileSync20(candidate, "utf-8")).version;
|
|
@@ -33069,7 +34980,7 @@ var init_report3 = __esm({
|
|
|
33069
34980
|
init_terminal_output();
|
|
33070
34981
|
init_workspace();
|
|
33071
34982
|
init_skill_version();
|
|
33072
|
-
here =
|
|
34983
|
+
here = dirname15(fileURLToPath2(import.meta.url));
|
|
33073
34984
|
}
|
|
33074
34985
|
});
|
|
33075
34986
|
|
|
@@ -33382,16 +35293,16 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
|
33382
35293
|
).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
|
|
33383
35294
|
const client = await createDataClient();
|
|
33384
35295
|
const id = pathSegment(tokenId, "token id");
|
|
33385
|
-
const
|
|
35296
|
+
const path41 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
|
|
33386
35297
|
const format = outputFormat(this);
|
|
33387
35298
|
const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
|
|
33388
35299
|
if (opts.force) {
|
|
33389
|
-
await client.request("DELETE",
|
|
35300
|
+
await client.request("DELETE", path41(true));
|
|
33390
35301
|
revoked(true);
|
|
33391
35302
|
return;
|
|
33392
35303
|
}
|
|
33393
35304
|
try {
|
|
33394
|
-
await client.request("DELETE",
|
|
35305
|
+
await client.request("DELETE", path41(false));
|
|
33395
35306
|
revoked(false);
|
|
33396
35307
|
} catch (err) {
|
|
33397
35308
|
if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
|
|
@@ -33406,7 +35317,7 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
|
33406
35317
|
console.error("Aborted.");
|
|
33407
35318
|
return process.exit(1);
|
|
33408
35319
|
}
|
|
33409
|
-
await client.request("DELETE",
|
|
35320
|
+
await client.request("DELETE", path41(true));
|
|
33410
35321
|
revoked(true);
|
|
33411
35322
|
}
|
|
33412
35323
|
});
|
|
@@ -33554,9 +35465,9 @@ var init_base_tags = __esm({
|
|
|
33554
35465
|
|
|
33555
35466
|
// src/data/commands/bases.ts
|
|
33556
35467
|
import { Command as Command10 } from "commander";
|
|
33557
|
-
function pageOf(
|
|
33558
|
-
if (!cursor) return
|
|
33559
|
-
return `${
|
|
35468
|
+
function pageOf(path41, cursor) {
|
|
35469
|
+
if (!cursor) return path41;
|
|
35470
|
+
return `${path41}${path41.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
|
|
33560
35471
|
}
|
|
33561
35472
|
function parseEnum(flag, value, allowed) {
|
|
33562
35473
|
if (value === void 0) return void 0;
|
|
@@ -33713,9 +35624,9 @@ function buildBasesCommand() {
|
|
|
33713
35624
|
});
|
|
33714
35625
|
bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
|
|
33715
35626
|
const client = await createDataClient();
|
|
33716
|
-
const
|
|
35627
|
+
const path41 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
|
|
33717
35628
|
printOutput(
|
|
33718
|
-
await client.collectPages((cursor) => pageOf(
|
|
35629
|
+
await client.collectPages((cursor) => pageOf(path41, cursor)),
|
|
33719
35630
|
outputFormat(this)
|
|
33720
35631
|
);
|
|
33721
35632
|
});
|
|
@@ -33762,9 +35673,9 @@ function buildBasesCommand() {
|
|
|
33762
35673
|
});
|
|
33763
35674
|
bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
|
|
33764
35675
|
const client = await createDataClient();
|
|
33765
|
-
const
|
|
35676
|
+
const path41 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
|
|
33766
35677
|
printOutput(
|
|
33767
|
-
await client.collectPages((cursor) => pageOf(
|
|
35678
|
+
await client.collectPages((cursor) => pageOf(path41, cursor)),
|
|
33768
35679
|
outputFormat(this)
|
|
33769
35680
|
);
|
|
33770
35681
|
});
|
|
@@ -33903,7 +35814,7 @@ var init_file_types = __esm({
|
|
|
33903
35814
|
// src/data/commands/files.ts
|
|
33904
35815
|
import { Command as Command12 } from "commander";
|
|
33905
35816
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
|
|
33906
|
-
import { basename as
|
|
35817
|
+
import { basename as basename25 } from "path";
|
|
33907
35818
|
function renderFileDiff(fileType, filePath, from, to, d) {
|
|
33908
35819
|
console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
|
|
33909
35820
|
const md = d.metadata_delta;
|
|
@@ -33935,7 +35846,7 @@ function renderFileDiff(fileType, filePath, from, to, d) {
|
|
|
33935
35846
|
}
|
|
33936
35847
|
function downloadTarget(remotePath, to) {
|
|
33937
35848
|
if (to) return to;
|
|
33938
|
-
const derived =
|
|
35849
|
+
const derived = basename25(remotePath);
|
|
33939
35850
|
if (derived === "" || derived === "." || derived === "..") {
|
|
33940
35851
|
throw expected(
|
|
33941
35852
|
`Cannot derive a local filename from "${remotePath}" \u2014 pass --to <local> to name it.`
|
|
@@ -34311,8 +36222,8 @@ function buildRecordsCommand() {
|
|
|
34311
36222
|
const body = { data: parseData(opts.data) };
|
|
34312
36223
|
if (opts.externalId) body.external_id = opts.externalId;
|
|
34313
36224
|
if (opts.externalSource) body.external_source = opts.externalSource;
|
|
34314
|
-
const
|
|
34315
|
-
printOutput(await client.request("PUT",
|
|
36225
|
+
const path41 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
|
|
36226
|
+
printOutput(await client.request("PUT", path41, body), outputFormat(this));
|
|
34316
36227
|
});
|
|
34317
36228
|
records.command("query <record_type>").description("List/search records: exact filters + fuzzy `search`, sorting, pagination").option(
|
|
34318
36229
|
"--external-source <source>",
|
|
@@ -34347,9 +36258,9 @@ function buildRecordsCommand() {
|
|
|
34347
36258
|
).action(async function(recordType2, id, opts) {
|
|
34348
36259
|
const base = pathSegment(requireBase(this), "--base");
|
|
34349
36260
|
const client = await createDataClient();
|
|
34350
|
-
let
|
|
34351
|
-
if (opts.externalSource)
|
|
34352
|
-
printOutput(await client.request("GET",
|
|
36261
|
+
let path41 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
|
|
36262
|
+
if (opts.externalSource) path41 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
|
|
36263
|
+
printOutput(await client.request("GET", path41), outputFormat(this));
|
|
34353
36264
|
});
|
|
34354
36265
|
records.command("delete <record_type> <id>").description(
|
|
34355
36266
|
"Delete a record by internal ID, or by external_id (pass --external-source to scope it)"
|
|
@@ -34363,11 +36274,11 @@ function buildRecordsCommand() {
|
|
|
34363
36274
|
}
|
|
34364
36275
|
const base = pathSegment(requireBase(this), "--base");
|
|
34365
36276
|
const client = await createDataClient();
|
|
34366
|
-
let
|
|
36277
|
+
let path41 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
|
|
34367
36278
|
if (opts.externalSource) {
|
|
34368
|
-
|
|
36279
|
+
path41 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
|
|
34369
36280
|
}
|
|
34370
|
-
await client.request("DELETE",
|
|
36281
|
+
await client.request("DELETE", path41);
|
|
34371
36282
|
console.log("Deleted");
|
|
34372
36283
|
});
|
|
34373
36284
|
records.command("history <id>").description(
|
|
@@ -34532,8 +36443,8 @@ function buildRelationshipsCommand() {
|
|
|
34532
36443
|
if (opts.data) body.data = parseData(opts.data);
|
|
34533
36444
|
const base = pathSegment(requireBase(this), "--base");
|
|
34534
36445
|
const client = await createDataClient();
|
|
34535
|
-
const
|
|
34536
|
-
printOutput(await client.request("PUT",
|
|
36446
|
+
const path41 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
|
|
36447
|
+
printOutput(await client.request("PUT", path41, body), outputFormat(this));
|
|
34537
36448
|
});
|
|
34538
36449
|
relationships.command("get <id>").description(
|
|
34539
36450
|
"Get a relationship by ID (or by its external key: pass the external_id with --rel-type)"
|
|
@@ -34766,8 +36677,8 @@ function buildToolsetsCommand() {
|
|
|
34766
36677
|
toolsets.command("get <slug>").description("Get a toolset").option("--resolved", "Include resolved record_type schemas").action(async function(slug, opts) {
|
|
34767
36678
|
const base = pathSegment(requireBase(this), "--base");
|
|
34768
36679
|
const client = await createDataClient();
|
|
34769
|
-
const
|
|
34770
|
-
printOutput(await client.request("GET",
|
|
36680
|
+
const path41 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
|
|
36681
|
+
printOutput(await client.request("GET", path41), outputFormat(this));
|
|
34771
36682
|
});
|
|
34772
36683
|
toolsets.command("list").description("List all toolsets").action(async function() {
|
|
34773
36684
|
const base = pathSegment(requireBase(this), "--base");
|
|
@@ -35002,7 +36913,7 @@ init_utils();
|
|
|
35002
36913
|
init_registry();
|
|
35003
36914
|
import { readFileSync as readFileSync24 } from "fs";
|
|
35004
36915
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
35005
|
-
import { dirname as
|
|
36916
|
+
import { dirname as dirname16, join as join37 } from "path";
|
|
35006
36917
|
|
|
35007
36918
|
// src/lib/version-refresh.ts
|
|
35008
36919
|
init_version_cache();
|
|
@@ -35157,8 +37068,8 @@ Run \`wayai admin skill install\` to update.`);
|
|
|
35157
37068
|
}
|
|
35158
37069
|
|
|
35159
37070
|
// src/index.ts
|
|
35160
|
-
var __dirname =
|
|
35161
|
-
var pkg = JSON.parse(readFileSync24(
|
|
37071
|
+
var __dirname = dirname16(fileURLToPath3(import.meta.url));
|
|
37072
|
+
var pkg = JSON.parse(readFileSync24(join37(__dirname, "..", "package.json"), "utf-8"));
|
|
35162
37073
|
var [, , command, ...args] = process.argv;
|
|
35163
37074
|
var isBackgroundRefresh = command === REFRESH_COMMAND;
|
|
35164
37075
|
if (!isBackgroundRefresh) initSentry(command, pkg.version);
|