@codio-ai/opencode-authoring-agent 0.1.4 → 0.1.5
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 +119 -86
- package/dist/tools/fetch-course.d.ts.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/publish-course.d.ts +1 -0
- package/dist/tools/publish-course.d.ts.map +1 -1
- package/dist/utils/load-env.d.ts +7 -0
- package/dist/utils/load-env.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3945,7 +3945,7 @@ var require_lodash = __commonJS((exports, module) => {
|
|
|
3945
3945
|
}
|
|
3946
3946
|
return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, undefined2, comparator) : [];
|
|
3947
3947
|
});
|
|
3948
|
-
function
|
|
3948
|
+
function join6(array, separator) {
|
|
3949
3949
|
return array == null ? "" : nativeJoin.call(array, separator);
|
|
3950
3950
|
}
|
|
3951
3951
|
function last(array) {
|
|
@@ -5882,7 +5882,7 @@ __p += '`;
|
|
|
5882
5882
|
lodash.isUndefined = isUndefined;
|
|
5883
5883
|
lodash.isWeakMap = isWeakMap;
|
|
5884
5884
|
lodash.isWeakSet = isWeakSet;
|
|
5885
|
-
lodash.join =
|
|
5885
|
+
lodash.join = join6;
|
|
5886
5886
|
lodash.kebabCase = kebabCase;
|
|
5887
5887
|
lodash.last = last;
|
|
5888
5888
|
lodash.lastIndexOf = lastIndexOf;
|
|
@@ -16380,11 +16380,11 @@ var require_path_arg = __commonJS((exports, module) => {
|
|
|
16380
16380
|
|
|
16381
16381
|
// node_modules/tar/node_modules/mkdirp/lib/find-made.js
|
|
16382
16382
|
var require_find_made = __commonJS((exports, module) => {
|
|
16383
|
-
var { dirname:
|
|
16383
|
+
var { dirname: dirname3 } = __require("path");
|
|
16384
16384
|
var findMade = (opts, parent, path = undefined) => {
|
|
16385
16385
|
if (path === parent)
|
|
16386
16386
|
return Promise.resolve();
|
|
16387
|
-
return opts.statAsync(parent).then((st) => st.isDirectory() ? path : undefined, (er) => er.code === "ENOENT" ? findMade(opts,
|
|
16387
|
+
return opts.statAsync(parent).then((st) => st.isDirectory() ? path : undefined, (er) => er.code === "ENOENT" ? findMade(opts, dirname3(parent), parent) : undefined);
|
|
16388
16388
|
};
|
|
16389
16389
|
var findMadeSync = (opts, parent, path = undefined) => {
|
|
16390
16390
|
if (path === parent)
|
|
@@ -16392,7 +16392,7 @@ var require_find_made = __commonJS((exports, module) => {
|
|
|
16392
16392
|
try {
|
|
16393
16393
|
return opts.statSync(parent).isDirectory() ? path : undefined;
|
|
16394
16394
|
} catch (er) {
|
|
16395
|
-
return er.code === "ENOENT" ? findMadeSync(opts,
|
|
16395
|
+
return er.code === "ENOENT" ? findMadeSync(opts, dirname3(parent), parent) : undefined;
|
|
16396
16396
|
}
|
|
16397
16397
|
};
|
|
16398
16398
|
module.exports = { findMade, findMadeSync };
|
|
@@ -16400,10 +16400,10 @@ var require_find_made = __commonJS((exports, module) => {
|
|
|
16400
16400
|
|
|
16401
16401
|
// node_modules/tar/node_modules/mkdirp/lib/mkdirp-manual.js
|
|
16402
16402
|
var require_mkdirp_manual = __commonJS((exports, module) => {
|
|
16403
|
-
var { dirname:
|
|
16403
|
+
var { dirname: dirname3 } = __require("path");
|
|
16404
16404
|
var mkdirpManual = (path, opts, made) => {
|
|
16405
16405
|
opts.recursive = false;
|
|
16406
|
-
const parent =
|
|
16406
|
+
const parent = dirname3(path);
|
|
16407
16407
|
if (parent === path) {
|
|
16408
16408
|
return opts.mkdirAsync(path, opts).catch((er) => {
|
|
16409
16409
|
if (er.code !== "EISDIR")
|
|
@@ -16426,7 +16426,7 @@ var require_mkdirp_manual = __commonJS((exports, module) => {
|
|
|
16426
16426
|
});
|
|
16427
16427
|
};
|
|
16428
16428
|
var mkdirpManualSync = (path, opts, made) => {
|
|
16429
|
-
const parent =
|
|
16429
|
+
const parent = dirname3(path);
|
|
16430
16430
|
opts.recursive = false;
|
|
16431
16431
|
if (parent === path) {
|
|
16432
16432
|
try {
|
|
@@ -16459,12 +16459,12 @@ var require_mkdirp_manual = __commonJS((exports, module) => {
|
|
|
16459
16459
|
|
|
16460
16460
|
// node_modules/tar/node_modules/mkdirp/lib/mkdirp-native.js
|
|
16461
16461
|
var require_mkdirp_native = __commonJS((exports, module) => {
|
|
16462
|
-
var { dirname:
|
|
16462
|
+
var { dirname: dirname3 } = __require("path");
|
|
16463
16463
|
var { findMade, findMadeSync } = require_find_made();
|
|
16464
16464
|
var { mkdirpManual, mkdirpManualSync } = require_mkdirp_manual();
|
|
16465
16465
|
var mkdirpNative = (path, opts) => {
|
|
16466
16466
|
opts.recursive = true;
|
|
16467
|
-
const parent =
|
|
16467
|
+
const parent = dirname3(path);
|
|
16468
16468
|
if (parent === path)
|
|
16469
16469
|
return opts.mkdirAsync(path, opts);
|
|
16470
16470
|
return findMade(opts, path).then((made) => opts.mkdirAsync(path, opts).then(() => made).catch((er) => {
|
|
@@ -16476,7 +16476,7 @@ var require_mkdirp_native = __commonJS((exports, module) => {
|
|
|
16476
16476
|
};
|
|
16477
16477
|
var mkdirpNativeSync = (path, opts) => {
|
|
16478
16478
|
opts.recursive = true;
|
|
16479
|
-
const parent =
|
|
16479
|
+
const parent = dirname3(path);
|
|
16480
16480
|
if (parent === path)
|
|
16481
16481
|
return opts.mkdirSync(path, opts);
|
|
16482
16482
|
const made = findMadeSync(opts, path);
|
|
@@ -16865,7 +16865,7 @@ var require_path_reservations = __commonJS((exports, module) => {
|
|
|
16865
16865
|
var assert = __require("assert");
|
|
16866
16866
|
var normalize = require_normalize_unicode();
|
|
16867
16867
|
var stripSlashes = require_strip_trailing_slashes();
|
|
16868
|
-
var { join:
|
|
16868
|
+
var { join: join6 } = __require("path");
|
|
16869
16869
|
var platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
|
|
16870
16870
|
var isWindows = platform === "win32";
|
|
16871
16871
|
module.exports = () => {
|
|
@@ -16874,7 +16874,7 @@ var require_path_reservations = __commonJS((exports, module) => {
|
|
|
16874
16874
|
const getDirs = (path) => {
|
|
16875
16875
|
const dirs = path.split("/").slice(0, -1).reduce((set, path2) => {
|
|
16876
16876
|
if (set.length) {
|
|
16877
|
-
path2 =
|
|
16877
|
+
path2 = join6(set[set.length - 1], path2);
|
|
16878
16878
|
}
|
|
16879
16879
|
set.push(path2 || "/");
|
|
16880
16880
|
return set;
|
|
@@ -16942,7 +16942,7 @@ var require_path_reservations = __commonJS((exports, module) => {
|
|
|
16942
16942
|
};
|
|
16943
16943
|
const reserve = (paths, fn) => {
|
|
16944
16944
|
paths = isWindows ? ["win32 parallelization disabled"] : paths.map((p) => {
|
|
16945
|
-
return stripSlashes(
|
|
16945
|
+
return stripSlashes(join6(normalize(p))).toLowerCase();
|
|
16946
16946
|
});
|
|
16947
16947
|
const dirs = new Set(paths.map((path) => getDirs(path)).reduce((a, b) => a.concat(b)));
|
|
16948
16948
|
reservations.set(fn, { dirs, paths });
|
|
@@ -18231,7 +18231,7 @@ var require_BufferList = __commonJS((exports, module) => {
|
|
|
18231
18231
|
this.head = this.tail = null;
|
|
18232
18232
|
this.length = 0;
|
|
18233
18233
|
};
|
|
18234
|
-
BufferList.prototype.join = function
|
|
18234
|
+
BufferList.prototype.join = function join6(s) {
|
|
18235
18235
|
if (this.length === 0)
|
|
18236
18236
|
return "";
|
|
18237
18237
|
var p = this.head;
|
|
@@ -20411,7 +20411,7 @@ var require_BufferList2 = __commonJS((exports, module) => {
|
|
|
20411
20411
|
this.head = this.tail = null;
|
|
20412
20412
|
this.length = 0;
|
|
20413
20413
|
};
|
|
20414
|
-
BufferList.prototype.join = function
|
|
20414
|
+
BufferList.prototype.join = function join6(s) {
|
|
20415
20415
|
if (this.length === 0)
|
|
20416
20416
|
return "";
|
|
20417
20417
|
var p = this.head;
|
|
@@ -22536,7 +22536,7 @@ var require_buffer_list = __commonJS((exports, module) => {
|
|
|
22536
22536
|
}
|
|
22537
22537
|
}, {
|
|
22538
22538
|
key: "join",
|
|
22539
|
-
value: function
|
|
22539
|
+
value: function join6(s) {
|
|
22540
22540
|
if (this.length === 0)
|
|
22541
22541
|
return "";
|
|
22542
22542
|
var p = this.head;
|
|
@@ -45914,7 +45914,7 @@ ${enabledAgents}
|
|
|
45914
45914
|
|
|
45915
45915
|
### Codio platform tools
|
|
45916
45916
|
- \`fetch_course({workspace, courseId, outputDir?, domain?})\` — pull an existing Codio course from the platform into an AI-ready local project. Requires \`CODIO_CLIENT_ID\` and \`CODIO_CLIENT_SECRET\` env vars. Returns \`{ outputDir, manifestPath, moduleCount, assignmentCount }\`. Writes \`course-manifest.json\`, \`course-assignments.csv\`, and extracted assignment content under \`course-content/\`. **Delegate when:** user wants to fetch, pull, import, or export an existing Codio course.
|
|
45917
|
-
- \`publish_course({manifestPath, dryRun?, force?, module?, assignment?, changelog?, stack?})\` — push course assignments back to the Codio platform using \`course-manifest.json\` as the source of truth. Creates missing courses/modules/assignments automatically. Skips unchanged assignments by content hash. Returns \`{ published, created, unchanged, skipped }\`. **Delegate when:** user wants to publish, push, deploy, or sync assignments to Codio. Always suggest \`dryRun: true\` first to preview.
|
|
45917
|
+
- \`publish_course({workspace, manifestPath, dryRun?, force?, module?, assignment?, changelog?, stack?})\` — push course assignments back to the Codio platform using \`course-manifest.json\` as the source of truth. Creates missing courses/modules/assignments automatically. Skips unchanged assignments by content hash. Returns \`{ published, created, unchanged, skipped }\`. **Delegate when:** user wants to publish, push, deploy, or sync assignments to Codio. Always suggest \`dryRun: true\` first to preview.
|
|
45918
45918
|
|
|
45919
45919
|
### Platform tool routing
|
|
45920
45920
|
- User says "fetch/pull/import/export this course" → \`fetch_course\`
|
|
@@ -47034,18 +47034,50 @@ ${freezeErrors.join(`
|
|
|
47034
47034
|
// src/tools/fetch-course.ts
|
|
47035
47035
|
import { execFile } from "node:child_process";
|
|
47036
47036
|
import {
|
|
47037
|
-
|
|
47037
|
+
copyFileSync,
|
|
47038
|
+
existsSync as existsSync4,
|
|
47038
47039
|
mkdirSync as mkdirSync2,
|
|
47039
47040
|
readdirSync,
|
|
47040
47041
|
rmSync,
|
|
47041
47042
|
statSync,
|
|
47042
47043
|
unlinkSync as unlinkSync2
|
|
47043
47044
|
} from "node:fs";
|
|
47044
|
-
import {
|
|
47045
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
47045
47046
|
import { tmpdir } from "node:os";
|
|
47046
|
-
import { dirname as
|
|
47047
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
47047
47048
|
import { promisify } from "node:util";
|
|
47048
47049
|
import { z as z4 } from "zod";
|
|
47050
|
+
|
|
47051
|
+
// src/utils/load-env.ts
|
|
47052
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
47053
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
47054
|
+
function loadEnvWalkUp(startDir) {
|
|
47055
|
+
let dir = startDir.replace(/\/+$/, "");
|
|
47056
|
+
while (true) {
|
|
47057
|
+
const candidate = join5(dir, ".env");
|
|
47058
|
+
if (existsSync3(candidate)) {
|
|
47059
|
+
for (const line of readFileSync2(candidate, "utf8").split(`
|
|
47060
|
+
`)) {
|
|
47061
|
+
const t = line.trim();
|
|
47062
|
+
if (!t || t.startsWith("#"))
|
|
47063
|
+
continue;
|
|
47064
|
+
const eq = t.indexOf("=");
|
|
47065
|
+
if (eq < 1)
|
|
47066
|
+
continue;
|
|
47067
|
+
const key = t.slice(0, eq).trim();
|
|
47068
|
+
const val = t.slice(eq + 1).trim().replace(/^(['"])(.*)\1$/, "$2");
|
|
47069
|
+
process.env[key] = val;
|
|
47070
|
+
}
|
|
47071
|
+
return;
|
|
47072
|
+
}
|
|
47073
|
+
const parent = dirname2(dir);
|
|
47074
|
+
if (parent === dir)
|
|
47075
|
+
return;
|
|
47076
|
+
dir = parent;
|
|
47077
|
+
}
|
|
47078
|
+
}
|
|
47079
|
+
|
|
47080
|
+
// src/tools/fetch-course.ts
|
|
47049
47081
|
var execFileAsync = promisify(execFile);
|
|
47050
47082
|
var fetchCourseInputSchema = z4.object({
|
|
47051
47083
|
workspace: z4.string().min(1).describe("Absolute path to the Codio workspace root"),
|
|
@@ -47063,7 +47095,7 @@ async function extractZip(zipPath, destDir) {
|
|
|
47063
47095
|
function findTarZstFiles(dir) {
|
|
47064
47096
|
const results = [];
|
|
47065
47097
|
for (const name of readdirSync(dir)) {
|
|
47066
|
-
const full =
|
|
47098
|
+
const full = join6(dir, name);
|
|
47067
47099
|
if (statSync(full).isDirectory())
|
|
47068
47100
|
results.push(...findTarZstFiles(full));
|
|
47069
47101
|
else if (name.endsWith(".tar.zst"))
|
|
@@ -47080,10 +47112,10 @@ async function extractTarZstArchives(stagingDir) {
|
|
|
47080
47112
|
}
|
|
47081
47113
|
}
|
|
47082
47114
|
function removeExportedReadmes(dir) {
|
|
47083
|
-
if (!
|
|
47115
|
+
if (!existsSync4(dir))
|
|
47084
47116
|
return;
|
|
47085
47117
|
for (const name of readdirSync(dir)) {
|
|
47086
|
-
const full =
|
|
47118
|
+
const full = join6(dir, name);
|
|
47087
47119
|
if (statSync(full).isDirectory())
|
|
47088
47120
|
removeExportedReadmes(full);
|
|
47089
47121
|
else if (name.toLowerCase() === "readme.md")
|
|
@@ -47093,18 +47125,17 @@ function removeExportedReadmes(dir) {
|
|
|
47093
47125
|
function copyDir(src, dst) {
|
|
47094
47126
|
mkdirSync2(dst, { recursive: true });
|
|
47095
47127
|
for (const name of readdirSync(src)) {
|
|
47096
|
-
const srcPath =
|
|
47097
|
-
const dstPath =
|
|
47128
|
+
const srcPath = join6(src, name);
|
|
47129
|
+
const dstPath = join6(dst, name);
|
|
47098
47130
|
if (statSync(srcPath).isDirectory())
|
|
47099
47131
|
copyDir(srcPath, dstPath);
|
|
47100
|
-
else
|
|
47101
|
-
const { copyFileSync } = __require("node:fs");
|
|
47132
|
+
else
|
|
47102
47133
|
copyFileSync(srcPath, dstPath);
|
|
47103
|
-
}
|
|
47104
47134
|
}
|
|
47105
47135
|
}
|
|
47106
47136
|
async function fetchCourseHandler(raw) {
|
|
47107
47137
|
const input = fetchCourseInputSchema.parse(raw);
|
|
47138
|
+
loadEnvWalkUp(input.workspace);
|
|
47108
47139
|
const [{ default: codio }, { json2csv }] = await Promise.all([
|
|
47109
47140
|
Promise.resolve().then(() => __toESM(require_lib3(), 1)),
|
|
47110
47141
|
Promise.resolve().then(() => __toESM(require_converter(), 1))
|
|
@@ -47112,14 +47143,14 @@ async function fetchCourseHandler(raw) {
|
|
|
47112
47143
|
const clientId = process.env["CODIO_CLIENT_ID"];
|
|
47113
47144
|
const clientSecret = process.env["CODIO_CLIENT_SECRET"];
|
|
47114
47145
|
if (!clientId || !clientSecret) {
|
|
47115
|
-
throw new Error("CODIO_CLIENT_ID and CODIO_CLIENT_SECRET must be set as environment variables
|
|
47146
|
+
throw new Error("CODIO_CLIENT_ID and CODIO_CLIENT_SECRET must be set in .env at the workspace root or as environment variables.");
|
|
47116
47147
|
}
|
|
47117
47148
|
const domain = input.domain ?? process.env["CODIO_DOMAIN"] ?? "codio.com";
|
|
47118
47149
|
const outputFolderName = input.outputDir ?? "course-project";
|
|
47119
|
-
const outputDir =
|
|
47120
|
-
const contentDir =
|
|
47121
|
-
const manifestPath =
|
|
47122
|
-
const csvPath =
|
|
47150
|
+
const outputDir = join6(input.workspace, outputFolderName);
|
|
47151
|
+
const contentDir = join6(outputDir, "course-content");
|
|
47152
|
+
const manifestPath = join6(outputDir, "course-manifest.json");
|
|
47153
|
+
const csvPath = join6(outputDir, "course-assignments.csv");
|
|
47123
47154
|
codio.v1.setDomain(domain);
|
|
47124
47155
|
await codio.v1.auth(clientId, clientSecret);
|
|
47125
47156
|
const courseInfo = await codio.v1.course.info(input.courseId);
|
|
@@ -47158,7 +47189,7 @@ async function fetchCourseHandler(raw) {
|
|
|
47158
47189
|
};
|
|
47159
47190
|
rmSync(outputDir, { recursive: true, force: true });
|
|
47160
47191
|
mkdirSync2(contentDir, { recursive: true });
|
|
47161
|
-
await mkdir(
|
|
47192
|
+
await mkdir(dirname3(manifestPath), { recursive: true });
|
|
47162
47193
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
47163
47194
|
`, "utf8");
|
|
47164
47195
|
const rows = modules.flatMap((m) => m.assignments.map((a) => ({
|
|
@@ -47172,12 +47203,12 @@ async function fetchCourseHandler(raw) {
|
|
|
47172
47203
|
})));
|
|
47173
47204
|
const csv = await json2csv(rows, { emptyFieldValue: "", prependHeader: true });
|
|
47174
47205
|
await writeFile(csvPath, csv, "utf8");
|
|
47175
|
-
const workDir =
|
|
47176
|
-
const zipPath =
|
|
47206
|
+
const workDir = join6(tmpdir(), `codio-fetch-${Date.now()}`);
|
|
47207
|
+
const zipPath = join6(workDir, "course-export.zip");
|
|
47177
47208
|
mkdirSync2(workDir, { recursive: true });
|
|
47178
47209
|
try {
|
|
47179
47210
|
await codio.v1.course.downloadSourceExport(input.courseId, zipPath);
|
|
47180
|
-
const stagingDir =
|
|
47211
|
+
const stagingDir = join6(workDir, "staging");
|
|
47181
47212
|
await extractZip(zipPath, stagingDir);
|
|
47182
47213
|
await extractTarZstArchives(stagingDir);
|
|
47183
47214
|
removeExportedReadmes(stagingDir);
|
|
@@ -47197,15 +47228,16 @@ async function fetchCourseHandler(raw) {
|
|
|
47197
47228
|
// src/tools/publish-course.ts
|
|
47198
47229
|
import { createHash } from "node:crypto";
|
|
47199
47230
|
import {
|
|
47200
|
-
existsSync as
|
|
47231
|
+
existsSync as existsSync5,
|
|
47201
47232
|
readdirSync as readdirSync2,
|
|
47202
|
-
readFileSync as
|
|
47203
|
-
writeFileSync as
|
|
47233
|
+
readFileSync as readFileSync3,
|
|
47234
|
+
writeFileSync as writeFileSync3
|
|
47204
47235
|
} from "node:fs";
|
|
47205
|
-
import { join as
|
|
47236
|
+
import { join as join7, relative } from "node:path";
|
|
47206
47237
|
import { z as z5 } from "zod";
|
|
47207
47238
|
var DEFAULT_STACK = "e0195698-d647-4490-8834-350583b532eb:latest";
|
|
47208
47239
|
var publishCourseInputSchema = z5.object({
|
|
47240
|
+
workspace: z5.string().min(1).describe("Absolute path to the Codio workspace root (where .env lives)"),
|
|
47209
47241
|
manifestPath: z5.string().min(1).describe("Absolute path to course-manifest.json"),
|
|
47210
47242
|
dryRun: z5.boolean().optional().describe("Preview actions without making changes"),
|
|
47211
47243
|
force: z5.boolean().optional().describe("Publish all, bypassing change detection"),
|
|
@@ -47215,17 +47247,17 @@ var publishCourseInputSchema = z5.object({
|
|
|
47215
47247
|
stack: z5.string().optional().describe("Force a specific stack ID for all assignments")
|
|
47216
47248
|
});
|
|
47217
47249
|
function saveManifest(manifestPath, manifest) {
|
|
47218
|
-
|
|
47250
|
+
writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
47219
47251
|
`, "utf8");
|
|
47220
47252
|
}
|
|
47221
47253
|
function findZip(dir) {
|
|
47222
47254
|
const zip = readdirSync2(dir).find((f) => f.endsWith(".zip"));
|
|
47223
|
-
return zip ?
|
|
47255
|
+
return zip ? join7(dir, zip) : null;
|
|
47224
47256
|
}
|
|
47225
47257
|
function walkFiles(dir) {
|
|
47226
47258
|
const results = [];
|
|
47227
47259
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
47228
|
-
const full =
|
|
47260
|
+
const full = join7(dir, entry.name);
|
|
47229
47261
|
if (entry.isDirectory())
|
|
47230
47262
|
results.push(...walkFiles(full));
|
|
47231
47263
|
else
|
|
@@ -47237,12 +47269,12 @@ function computeContentHash(dir) {
|
|
|
47237
47269
|
const hash = createHash("sha256");
|
|
47238
47270
|
const zipPath = findZip(dir);
|
|
47239
47271
|
if (zipPath) {
|
|
47240
|
-
hash.update(
|
|
47272
|
+
hash.update(readFileSync3(zipPath));
|
|
47241
47273
|
} else {
|
|
47242
47274
|
for (const file of walkFiles(dir).sort()) {
|
|
47243
|
-
hash.update(
|
|
47275
|
+
hash.update(relative(dir, file));
|
|
47244
47276
|
hash.update("\x00");
|
|
47245
|
-
hash.update(
|
|
47277
|
+
hash.update(readFileSync3(file));
|
|
47246
47278
|
hash.update("\x00");
|
|
47247
47279
|
}
|
|
47248
47280
|
}
|
|
@@ -47250,20 +47282,21 @@ function computeContentHash(dir) {
|
|
|
47250
47282
|
}
|
|
47251
47283
|
async function publishCourseHandler(raw) {
|
|
47252
47284
|
const input = publishCourseInputSchema.parse(raw);
|
|
47285
|
+
loadEnvWalkUp(input.workspace);
|
|
47253
47286
|
const { default: codio } = await Promise.resolve().then(() => __toESM(require_lib3(), 1));
|
|
47254
47287
|
const clientId = process.env["CODIO_CLIENT_ID"];
|
|
47255
47288
|
const clientSecret = process.env["CODIO_CLIENT_SECRET"];
|
|
47256
47289
|
if (!clientId || !clientSecret) {
|
|
47257
47290
|
throw new Error("CODIO_CLIENT_ID and CODIO_CLIENT_SECRET must be set as environment variables on the box.");
|
|
47258
47291
|
}
|
|
47259
|
-
if (!
|
|
47292
|
+
if (!existsSync5(input.manifestPath)) {
|
|
47260
47293
|
throw new Error(`course-manifest.json not found at ${input.manifestPath}`);
|
|
47261
47294
|
}
|
|
47262
47295
|
const domain = process.env["CODIO_DOMAIN"] ?? "codio.com";
|
|
47263
47296
|
codio.v1.setDomain(domain);
|
|
47264
47297
|
await codio.v1.auth(clientId, clientSecret);
|
|
47265
|
-
const manifest = JSON.parse(
|
|
47266
|
-
const manifestDir =
|
|
47298
|
+
const manifest = JSON.parse(readFileSync3(input.manifestPath, "utf8"));
|
|
47299
|
+
const manifestDir = join7(input.manifestPath, "..");
|
|
47267
47300
|
if (!manifest.courseId) {
|
|
47268
47301
|
const courseName = manifest.courseName ?? "Codio Course";
|
|
47269
47302
|
if (input.dryRun) {
|
|
@@ -47305,8 +47338,8 @@ async function publishCourseHandler(raw) {
|
|
|
47305
47338
|
for (const asgn of mod.assignments) {
|
|
47306
47339
|
if (input.assignment && asgn.name !== input.assignment && asgn.folder !== input.assignment)
|
|
47307
47340
|
continue;
|
|
47308
|
-
const asgnAbsPath =
|
|
47309
|
-
if (!
|
|
47341
|
+
const asgnAbsPath = join7(manifestDir, asgn.path);
|
|
47342
|
+
if (!existsSync5(asgnAbsPath)) {
|
|
47310
47343
|
skipped++;
|
|
47311
47344
|
continue;
|
|
47312
47345
|
}
|
|
@@ -47360,19 +47393,19 @@ async function publishCourseHandler(raw) {
|
|
|
47360
47393
|
}
|
|
47361
47394
|
|
|
47362
47395
|
// src/tools/validate-guide.ts
|
|
47363
|
-
import { existsSync as
|
|
47364
|
-
import { join as
|
|
47396
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
47397
|
+
import { join as join8, relative as relative2 } from "node:path";
|
|
47365
47398
|
import { z as z6 } from "zod";
|
|
47366
47399
|
var validateGuideInputSchema = z6.object({
|
|
47367
47400
|
workspace: z6.string().min(1)
|
|
47368
47401
|
});
|
|
47369
47402
|
function walkJsonFiles(root) {
|
|
47370
|
-
if (!
|
|
47403
|
+
if (!existsSync6(root))
|
|
47371
47404
|
return [];
|
|
47372
47405
|
const out = [];
|
|
47373
47406
|
for (const name of readdirSync3(root)) {
|
|
47374
|
-
const p =
|
|
47375
|
-
const s =
|
|
47407
|
+
const p = join8(root, name);
|
|
47408
|
+
const s = statSync2(p);
|
|
47376
47409
|
if (s.isDirectory())
|
|
47377
47410
|
out.push(...walkJsonFiles(p));
|
|
47378
47411
|
else if (name.endsWith(".json"))
|
|
@@ -47398,12 +47431,12 @@ var LANG_COMMENT_PREFIX2 = {
|
|
|
47398
47431
|
sql: "--"
|
|
47399
47432
|
};
|
|
47400
47433
|
function walkMdFiles(root) {
|
|
47401
|
-
if (!
|
|
47434
|
+
if (!existsSync6(root))
|
|
47402
47435
|
return [];
|
|
47403
47436
|
const out = [];
|
|
47404
47437
|
for (const name of readdirSync3(root)) {
|
|
47405
|
-
const p =
|
|
47406
|
-
const s =
|
|
47438
|
+
const p = join8(root, name);
|
|
47439
|
+
const s = statSync2(p);
|
|
47407
47440
|
if (s.isDirectory())
|
|
47408
47441
|
out.push(...walkMdFiles(p));
|
|
47409
47442
|
else if (name.endsWith(".md"))
|
|
@@ -47476,7 +47509,7 @@ async function validateGuideHandler(raw) {
|
|
|
47476
47509
|
directory: input.workspace
|
|
47477
47510
|
});
|
|
47478
47511
|
const findings = [];
|
|
47479
|
-
if (!
|
|
47512
|
+
if (!existsSync6(content)) {
|
|
47480
47513
|
findings.push({
|
|
47481
47514
|
category: "missing_content_dir",
|
|
47482
47515
|
detail: `${content} does not exist`,
|
|
@@ -47487,7 +47520,7 @@ async function validateGuideHandler(raw) {
|
|
|
47487
47520
|
for (const f of walkJsonFiles(guides)) {
|
|
47488
47521
|
let parsed;
|
|
47489
47522
|
try {
|
|
47490
|
-
parsed = JSON.parse(
|
|
47523
|
+
parsed = JSON.parse(readFileSync4(f, "utf8"));
|
|
47491
47524
|
} catch {
|
|
47492
47525
|
continue;
|
|
47493
47526
|
}
|
|
@@ -47497,7 +47530,7 @@ async function validateGuideHandler(raw) {
|
|
|
47497
47530
|
if (!isUuidV4(id)) {
|
|
47498
47531
|
findings.push({
|
|
47499
47532
|
category: "invalid_uuid",
|
|
47500
|
-
detail: `${
|
|
47533
|
+
detail: `${relative2(input.workspace, f)}: ${id}`,
|
|
47501
47534
|
severity: "error"
|
|
47502
47535
|
});
|
|
47503
47536
|
}
|
|
@@ -47508,45 +47541,45 @@ async function validateGuideHandler(raw) {
|
|
|
47508
47541
|
continue;
|
|
47509
47542
|
let parsed;
|
|
47510
47543
|
try {
|
|
47511
|
-
parsed = JSON.parse(
|
|
47544
|
+
parsed = JSON.parse(readFileSync4(f, "utf8"));
|
|
47512
47545
|
} catch {
|
|
47513
47546
|
continue;
|
|
47514
47547
|
}
|
|
47515
47548
|
const base = f.slice(0, -"/index.json".length);
|
|
47516
47549
|
for (const stem of parsed?.order ?? []) {
|
|
47517
|
-
const jsonPair =
|
|
47518
|
-
const chapter =
|
|
47550
|
+
const jsonPair = existsSync6(join8(base, `${stem}.json`)) && existsSync6(join8(base, `${stem}.md`));
|
|
47551
|
+
const chapter = existsSync6(join8(base, stem, "index.json"));
|
|
47519
47552
|
if (!jsonPair && !chapter) {
|
|
47520
47553
|
findings.push({
|
|
47521
47554
|
category: "orphan_order_stem",
|
|
47522
|
-
detail: `${
|
|
47555
|
+
detail: `${relative2(input.workspace, f)} references missing stem '${stem}'`,
|
|
47523
47556
|
severity: "error"
|
|
47524
47557
|
});
|
|
47525
47558
|
}
|
|
47526
47559
|
}
|
|
47527
47560
|
}
|
|
47528
47561
|
for (const md of walkMdFiles(content)) {
|
|
47529
|
-
const body =
|
|
47562
|
+
const body = readFileSync4(md, "utf8");
|
|
47530
47563
|
const re = new RegExp(ANY_TASKID_RE.source, "g");
|
|
47531
47564
|
for (const match of body.matchAll(re)) {
|
|
47532
47565
|
const tid = match[0];
|
|
47533
|
-
if (!
|
|
47566
|
+
if (!existsSync6(join8(assessments, `${tid}.json`))) {
|
|
47534
47567
|
findings.push({
|
|
47535
47568
|
category: "broken_assessment_ref",
|
|
47536
|
-
detail: `${
|
|
47569
|
+
detail: `${relative2(input.workspace, md)} references missing ${tid}.json`,
|
|
47537
47570
|
severity: "error"
|
|
47538
47571
|
});
|
|
47539
47572
|
}
|
|
47540
47573
|
}
|
|
47541
47574
|
}
|
|
47542
|
-
if (
|
|
47575
|
+
if (existsSync6(assessments)) {
|
|
47543
47576
|
for (const f of walkJsonFiles(assessments)) {
|
|
47544
47577
|
if (!f.endsWith(".json"))
|
|
47545
47578
|
continue;
|
|
47546
47579
|
const expected = f.split("/").pop().replace(/\.json$/, "");
|
|
47547
47580
|
let parsed;
|
|
47548
47581
|
try {
|
|
47549
|
-
parsed = JSON.parse(
|
|
47582
|
+
parsed = JSON.parse(readFileSync4(f, "utf8"));
|
|
47550
47583
|
} catch {
|
|
47551
47584
|
continue;
|
|
47552
47585
|
}
|
|
@@ -47554,7 +47587,7 @@ async function validateGuideHandler(raw) {
|
|
|
47554
47587
|
if (expected !== actual) {
|
|
47555
47588
|
findings.push({
|
|
47556
47589
|
category: "taskid_filename_mismatch",
|
|
47557
|
-
detail: `${
|
|
47590
|
+
detail: `${relative2(input.workspace, f)}: filename '${expected}' vs taskId '${actual}'`,
|
|
47558
47591
|
severity: "error"
|
|
47559
47592
|
});
|
|
47560
47593
|
}
|
|
@@ -47565,7 +47598,7 @@ async function validateGuideHandler(raw) {
|
|
|
47565
47598
|
continue;
|
|
47566
47599
|
let pageTitle;
|
|
47567
47600
|
try {
|
|
47568
|
-
const parsed = JSON.parse(
|
|
47601
|
+
const parsed = JSON.parse(readFileSync4(f, "utf8"));
|
|
47569
47602
|
pageTitle = parsed?.title;
|
|
47570
47603
|
} catch {
|
|
47571
47604
|
continue;
|
|
@@ -47573,34 +47606,34 @@ async function validateGuideHandler(raw) {
|
|
|
47573
47606
|
if (!pageTitle)
|
|
47574
47607
|
continue;
|
|
47575
47608
|
const mdPath = f.replace(/\.json$/, ".md");
|
|
47576
|
-
if (!
|
|
47609
|
+
if (!existsSync6(mdPath))
|
|
47577
47610
|
continue;
|
|
47578
|
-
const body =
|
|
47611
|
+
const body = readFileSync4(mdPath, "utf8");
|
|
47579
47612
|
const firstH1 = findFirstH1(body);
|
|
47580
47613
|
if (firstH1 && firstH1 === pageTitle) {
|
|
47581
47614
|
findings.push({
|
|
47582
47615
|
category: "double_header",
|
|
47583
|
-
detail: `${
|
|
47616
|
+
detail: `${relative2(input.workspace, mdPath)}: first H1 "${firstH1}" duplicates page title — remove H1 from body`,
|
|
47584
47617
|
severity: "warning"
|
|
47585
47618
|
});
|
|
47586
47619
|
}
|
|
47587
47620
|
}
|
|
47588
47621
|
for (const md of walkMdFiles(content)) {
|
|
47589
|
-
const body =
|
|
47622
|
+
const body = readFileSync4(md, "utf8");
|
|
47590
47623
|
for (const err of checkFreezeDirectives(body)) {
|
|
47591
47624
|
findings.push({
|
|
47592
47625
|
category: "freeze_syntax_error",
|
|
47593
|
-
detail: `${
|
|
47626
|
+
detail: `${relative2(input.workspace, md)}: ${err}`,
|
|
47594
47627
|
severity: "error"
|
|
47595
47628
|
});
|
|
47596
47629
|
}
|
|
47597
47630
|
}
|
|
47598
47631
|
for (const md of walkMdFiles(content)) {
|
|
47599
|
-
const body =
|
|
47632
|
+
const body = readFileSync4(md, "utf8");
|
|
47600
47633
|
for (const err of findMalformedTryIt(body)) {
|
|
47601
47634
|
findings.push({
|
|
47602
47635
|
category: "try_it_syntax_error",
|
|
47603
|
-
detail: `${
|
|
47636
|
+
detail: `${relative2(input.workspace, md)}: ${err}`,
|
|
47604
47637
|
severity: "error"
|
|
47605
47638
|
});
|
|
47606
47639
|
}
|
|
@@ -47610,7 +47643,7 @@ async function validateGuideHandler(raw) {
|
|
|
47610
47643
|
continue;
|
|
47611
47644
|
let parsed;
|
|
47612
47645
|
try {
|
|
47613
|
-
parsed = JSON.parse(
|
|
47646
|
+
parsed = JSON.parse(readFileSync4(f, "utf8"));
|
|
47614
47647
|
} catch {
|
|
47615
47648
|
continue;
|
|
47616
47649
|
}
|
|
@@ -47618,10 +47651,10 @@ async function validateGuideHandler(raw) {
|
|
|
47618
47651
|
const filePath = entry?.path;
|
|
47619
47652
|
if (!filePath)
|
|
47620
47653
|
continue;
|
|
47621
|
-
if (!
|
|
47654
|
+
if (!existsSync6(join8(input.workspace, filePath))) {
|
|
47622
47655
|
findings.push({
|
|
47623
47656
|
category: "missing_referenced_file",
|
|
47624
|
-
detail: `${
|
|
47657
|
+
detail: `${relative2(input.workspace, f)}: references file "${filePath}" which does not exist in workspace`,
|
|
47625
47658
|
severity: "warning"
|
|
47626
47659
|
});
|
|
47627
47660
|
}
|
|
@@ -47665,7 +47698,7 @@ var fetch_course = tool({
|
|
|
47665
47698
|
}
|
|
47666
47699
|
});
|
|
47667
47700
|
var publish_course = tool({
|
|
47668
|
-
description: "Publish Codio course assignments to the platform. Reads course-manifest.json, creates missing courses/modules/assignments, and publishes each one. Skips unchanged assignments by content hash. Returns { published, created, unchanged, skipped }.",
|
|
47701
|
+
description: "Publish Codio course assignments to the platform. Reads course-manifest.json from manifestPath, creates missing courses/modules/assignments, and publishes each one. Skips unchanged assignments by content hash. Returns { published, created, unchanged, skipped }.",
|
|
47669
47702
|
args: publishCourseInputSchema.shape,
|
|
47670
47703
|
async execute(args) {
|
|
47671
47704
|
return JSON.stringify(await publishCourseHandler(args));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-course.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-course.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch-course.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-course.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,sBAAsB;;;;;iBAQjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAyDD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA8GjF"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export declare const fetch_course: {
|
|
|
76
76
|
export declare const publish_course: {
|
|
77
77
|
description: string;
|
|
78
78
|
args: {
|
|
79
|
+
workspace: import("zod").ZodString;
|
|
79
80
|
manifestPath: import("zod").ZodString;
|
|
80
81
|
dryRun: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
81
82
|
force: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
@@ -85,6 +86,7 @@ export declare const publish_course: {
|
|
|
85
86
|
stack: import("zod").ZodOptional<import("zod").ZodString>;
|
|
86
87
|
};
|
|
87
88
|
execute(args: {
|
|
89
|
+
workspace: string;
|
|
88
90
|
manifestPath: string;
|
|
89
91
|
dryRun?: boolean | undefined;
|
|
90
92
|
force?: boolean | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOtB,CAAC;AAOH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAkB5B,CAAC;AAIH,eAAO,MAAM,cAAc;;;;;;;;CAOzB,CAAC;AAIH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAOvB,CAAC;AAIH,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOtB,CAAC;AAOH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;CAkB5B,CAAC;AAIH,eAAO,MAAM,cAAc;;;;;;;;CAOzB,CAAC;AAIH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAOvB,CAAC;AAIH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;CAOzB,CAAC;AAQH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAMzC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-course.d.ts","sourceRoot":"","sources":["../../src/tools/publish-course.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"publish-course.d.ts","sourceRoot":"","sources":["../../src/tools/publish-course.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,wBAAwB;;;;;;;;;iBASnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AA6DD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA2HrF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk up from startDir looking for a .env file.
|
|
3
|
+
* When found, load it with override semantics — .env values always win
|
|
4
|
+
* over whatever is already in process.env.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadEnvWalkUp(startDir: string): void;
|
|
7
|
+
//# sourceMappingURL=load-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-env.d.ts","sourceRoot":"","sources":["../../src/utils/load-env.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAoBpD"}
|
package/package.json
CHANGED