@playcademy/vite-plugin 1.2.1-beta.21 → 1.2.1-beta.22
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 +272 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -24315,7 +24315,7 @@ import path2 from "node:path";
|
|
|
24315
24315
|
// package.json
|
|
24316
24316
|
var package_default = {
|
|
24317
24317
|
name: "@playcademy/vite-plugin",
|
|
24318
|
-
version: "1.2.1-beta.
|
|
24318
|
+
version: "1.2.1-beta.22",
|
|
24319
24319
|
type: "module",
|
|
24320
24320
|
exports: {
|
|
24321
24321
|
".": {
|
|
@@ -25921,7 +25921,7 @@ var package_default2;
|
|
|
25921
25921
|
var init_package = __esm(() => {
|
|
25922
25922
|
package_default2 = {
|
|
25923
25923
|
name: "@playcademy/sandbox",
|
|
25924
|
-
version: "0.7.1-beta.
|
|
25924
|
+
version: "0.7.1-beta.21",
|
|
25925
25925
|
description: "Local development server for Playcademy game development",
|
|
25926
25926
|
type: "module",
|
|
25927
25927
|
exports: {
|
|
@@ -64252,6 +64252,8 @@ var AssessmentStandardRefSchema2;
|
|
|
64252
64252
|
var CreateAssessmentRequestSchema;
|
|
64253
64253
|
var UpdateAssessmentRequestSchema;
|
|
64254
64254
|
var CopyAssessmentRequestSchema;
|
|
64255
|
+
var AssessmentAssociationImportEntrySchema;
|
|
64256
|
+
var AttachExistingAssessmentsRequestSchema;
|
|
64255
64257
|
var ReorderAssessmentsRequestSchema;
|
|
64256
64258
|
var ReorderQuestionsRequestSchema;
|
|
64257
64259
|
var init_schemas4 = __esm(() => {
|
|
@@ -64520,6 +64522,43 @@ var init_schemas4 = __esm(() => {
|
|
|
64520
64522
|
purpose: AssessmentPurposeSchema,
|
|
64521
64523
|
standard: AssessmentStandardRefSchema2.optional()
|
|
64522
64524
|
}).superRefine(requireMasteryStandard);
|
|
64525
|
+
AssessmentAssociationImportEntrySchema = exports_external.object({
|
|
64526
|
+
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
64527
|
+
purpose: AssessmentPurposeSchema,
|
|
64528
|
+
standard: AssessmentStandardRefSchema2.optional(),
|
|
64529
|
+
sortOrder: exports_external.number().int().nonnegative().nullable()
|
|
64530
|
+
}).superRefine((input, context2) => {
|
|
64531
|
+
requireMasteryStandard(input, context2);
|
|
64532
|
+
if (input.purpose !== "mastery" && input.standard !== undefined) {
|
|
64533
|
+
context2.addIssue({
|
|
64534
|
+
code: "custom",
|
|
64535
|
+
path: ["standard"],
|
|
64536
|
+
message: "Only mastery assessments may have a quiz-level standard"
|
|
64537
|
+
});
|
|
64538
|
+
}
|
|
64539
|
+
});
|
|
64540
|
+
AttachExistingAssessmentsRequestSchema = exports_external.object({
|
|
64541
|
+
version: exports_external.literal(1),
|
|
64542
|
+
sourceGameId: exports_external.string().trim().min(1),
|
|
64543
|
+
sourceGameSlug: exports_external.string().trim().min(1).optional(),
|
|
64544
|
+
sourceIntegrationId: exports_external.string().trim().min(1),
|
|
64545
|
+
subject: TimebackSubjectSchema,
|
|
64546
|
+
grade: TimebackGradeSchema,
|
|
64547
|
+
targetStatus: exports_external.enum(["draft", "live"]),
|
|
64548
|
+
assessments: exports_external.array(AssessmentAssociationImportEntrySchema).min(1, "At least one assessment is required").max(500, "A bulk import can contain at most 500 assessments")
|
|
64549
|
+
}).superRefine((input, context2) => {
|
|
64550
|
+
const seen = new Set;
|
|
64551
|
+
input.assessments.forEach((assessment, index2) => {
|
|
64552
|
+
if (seen.has(assessment.qtiTestIdentifier)) {
|
|
64553
|
+
context2.addIssue({
|
|
64554
|
+
code: "custom",
|
|
64555
|
+
path: ["assessments", index2, "qtiTestIdentifier"],
|
|
64556
|
+
message: "Assessment identifiers must be unique"
|
|
64557
|
+
});
|
|
64558
|
+
}
|
|
64559
|
+
seen.add(assessment.qtiTestIdentifier);
|
|
64560
|
+
});
|
|
64561
|
+
});
|
|
64523
64562
|
ReorderAssessmentsRequestSchema = exports_external.object({
|
|
64524
64563
|
purpose: AssessmentPurposeSchema,
|
|
64525
64564
|
testIdentifiers: exports_external.array(exports_external.string().trim().min(1, "Assessment identifier is required")).min(1, "At least one assessment is required")
|
|
@@ -120014,6 +120053,74 @@ var init_timeback_qti_authoring_util = __esm(() => {
|
|
|
120014
120053
|
buildMatchQuestionXml
|
|
120015
120054
|
];
|
|
120016
120055
|
});
|
|
120056
|
+
function assertPlayableAssessmentImportQuestions(targetStatus, questions) {
|
|
120057
|
+
if (targetStatus === "draft") {
|
|
120058
|
+
return;
|
|
120059
|
+
}
|
|
120060
|
+
for (const question of questions) {
|
|
120061
|
+
assertPlayableQtiQuestion(question);
|
|
120062
|
+
}
|
|
120063
|
+
}
|
|
120064
|
+
function assertAssessmentImportCourseMatches(manifest, integration) {
|
|
120065
|
+
if (integration.subject !== manifest.subject || integration.grade !== manifest.grade) {
|
|
120066
|
+
throw new ValidationError(`This manifest is for ${manifest.subject} grade ${manifest.grade}, not ${integration.subject} grade ${integration.grade}.`);
|
|
120067
|
+
}
|
|
120068
|
+
}
|
|
120069
|
+
function associationMetadataMatches(row, entry2) {
|
|
120070
|
+
return row.purpose === entry2.purpose && (row.standardFramework ?? undefined) === entry2.standard?.framework && (row.standardIdentifier ?? undefined) === entry2.standard?.identifier;
|
|
120071
|
+
}
|
|
120072
|
+
function targetStatusSatisfied(row, targetStatus) {
|
|
120073
|
+
return row.status === targetStatus || targetStatus === "draft" && row.status === "live";
|
|
120074
|
+
}
|
|
120075
|
+
function classifyExistingAssessmentImport(row, entry2, targetStatus) {
|
|
120076
|
+
if (!associationMetadataMatches(row, entry2)) {
|
|
120077
|
+
return {
|
|
120078
|
+
status: "failed",
|
|
120079
|
+
message: "Already attached with different purpose or standard metadata."
|
|
120080
|
+
};
|
|
120081
|
+
}
|
|
120082
|
+
if (targetStatusSatisfied(row, targetStatus)) {
|
|
120083
|
+
return { status: "skipped", message: `Already attached as ${row.status}.` };
|
|
120084
|
+
}
|
|
120085
|
+
if (row.status === "archived") {
|
|
120086
|
+
return {
|
|
120087
|
+
status: "failed",
|
|
120088
|
+
message: "Already attached as archived and cannot be imported again."
|
|
120089
|
+
};
|
|
120090
|
+
}
|
|
120091
|
+
if (targetStatus === "live" && row.status === "draft") {
|
|
120092
|
+
return {
|
|
120093
|
+
status: "failed",
|
|
120094
|
+
message: "Already attached as a draft. Publish it from the assessment editor before retrying."
|
|
120095
|
+
};
|
|
120096
|
+
}
|
|
120097
|
+
return {
|
|
120098
|
+
status: "failed",
|
|
120099
|
+
message: `Already attached as ${row.status}, which does not match the requested ${targetStatus} status.`
|
|
120100
|
+
};
|
|
120101
|
+
}
|
|
120102
|
+
function attachedAssessmentImportMessage(targetStatus, editable) {
|
|
120103
|
+
if (targetStatus === "live") {
|
|
120104
|
+
return editable ? "Attached live." : "Attached live. QTI content is read-only because another app owns it.";
|
|
120105
|
+
}
|
|
120106
|
+
return editable ? "Attached as a draft." : "Attached as a read-only draft owned by another app.";
|
|
120107
|
+
}
|
|
120108
|
+
function liveReviewAssessmentImportFailures(targetStatus, candidates, existingRows) {
|
|
120109
|
+
if (targetStatus === "draft") {
|
|
120110
|
+
return new Map;
|
|
120111
|
+
}
|
|
120112
|
+
const reviews = candidates.filter((candidate) => candidate.purpose === "review");
|
|
120113
|
+
const hasExistingLiveReview = existingRows.some((row) => row.purpose === "review" && row.status === "live");
|
|
120114
|
+
if (reviews.length <= 1 && !hasExistingLiveReview) {
|
|
120115
|
+
return new Map;
|
|
120116
|
+
}
|
|
120117
|
+
const message = hasExistingLiveReview ? "Another review assessment is already live for this course." : "Only one review assessment can be imported live at a time.";
|
|
120118
|
+
return new Map(reviews.map((review) => [review.qtiTestIdentifier, message]));
|
|
120119
|
+
}
|
|
120120
|
+
var init_timeback_assessment_import_util = __esm(() => {
|
|
120121
|
+
init_errors2();
|
|
120122
|
+
init_timeback_qti_authoring_util();
|
|
120123
|
+
});
|
|
120017
120124
|
function newPlaycademyTestIdentifier() {
|
|
120018
120125
|
return `${PLAYCADEMY_QTI_SOURCE_PREFIX}${crypto.randomUUID()}`;
|
|
120019
120126
|
}
|
|
@@ -120160,6 +120267,150 @@ class TimebackAssessmentsService {
|
|
|
120160
120267
|
throw error88;
|
|
120161
120268
|
}
|
|
120162
120269
|
}
|
|
120270
|
+
async attachExistingAssessments(integrationId, manifest) {
|
|
120271
|
+
const client2 = this.requireClient();
|
|
120272
|
+
const { integration, gameSlug } = await this.requireQtiTestOwnershipContext(integrationId);
|
|
120273
|
+
assertAssessmentImportCourseMatches(manifest, integration);
|
|
120274
|
+
validateUniqueAssessmentIdentifiers(manifest.assessments.map((assessment) => assessment.qtiTestIdentifier));
|
|
120275
|
+
const entries = manifest.assessments.map((assessment, index2) => ({
|
|
120276
|
+
...assessment,
|
|
120277
|
+
index: index2,
|
|
120278
|
+
standard: this.requirePurposeStandard(assessment.purpose, assessment.standard)
|
|
120279
|
+
}));
|
|
120280
|
+
const existingRows = await this.deps.db.query.gameTimebackAssessmentTests.findMany({
|
|
120281
|
+
where: eq(gameTimebackAssessmentTests.integrationId, integrationId)
|
|
120282
|
+
});
|
|
120283
|
+
const existingByIdentifier = new Map(existingRows.map((row) => [row.qtiTestIdentifier, row]));
|
|
120284
|
+
const validated = await runWithConcurrency(entries, QTI_HYDRATION_CONCURRENCY, async (entry2) => {
|
|
120285
|
+
try {
|
|
120286
|
+
const loaded = await loadHydratedQtiTest(client2, entry2.qtiTestIdentifier);
|
|
120287
|
+
assertAssessmentHasQuestions(loaded.questions.questions);
|
|
120288
|
+
assertPlayableAssessmentImportQuestions(manifest.targetStatus, loaded.questions.questions.map(({ question }) => question));
|
|
120289
|
+
if (entry2.purpose === "review") {
|
|
120290
|
+
assertReviewAssessmentHasStandards(loaded.questions.questions.map(({ question }) => qtiAssessmentStandardRefsFromMetadata2(question.metadata).filter((standard) => canonicalReviewStandardRef(standard) !== null).length));
|
|
120291
|
+
}
|
|
120292
|
+
return { entry: entry2, test: loaded.test };
|
|
120293
|
+
} catch (error88) {
|
|
120294
|
+
return { entry: entry2, error: error88 };
|
|
120295
|
+
}
|
|
120296
|
+
});
|
|
120297
|
+
const results = [];
|
|
120298
|
+
const pending = [];
|
|
120299
|
+
for (const validation of validated) {
|
|
120300
|
+
const { entry: entry2 } = validation;
|
|
120301
|
+
const { index: index2 } = entry2;
|
|
120302
|
+
const base = {
|
|
120303
|
+
qtiTestIdentifier: entry2.qtiTestIdentifier,
|
|
120304
|
+
purpose: entry2.purpose,
|
|
120305
|
+
...entry2.standard ? { standard: entry2.standard } : {},
|
|
120306
|
+
sourceSortOrder: entry2.sortOrder
|
|
120307
|
+
};
|
|
120308
|
+
if ("error" in validation) {
|
|
120309
|
+
results[index2] = {
|
|
120310
|
+
...base,
|
|
120311
|
+
status: "failed",
|
|
120312
|
+
message: `QTI validation failed: ${errorMessage2(validation.error)}`
|
|
120313
|
+
};
|
|
120314
|
+
} else {
|
|
120315
|
+
const existing = existingByIdentifier.get(entry2.qtiTestIdentifier);
|
|
120316
|
+
const editable = isQtiTestOwnedByGame(validation.test, gameSlug);
|
|
120317
|
+
const hydratedBase = { ...base, title: validation.test.title, editable };
|
|
120318
|
+
if (!existing) {
|
|
120319
|
+
pending.push({ entry: entry2, test: validation.test });
|
|
120320
|
+
} else {
|
|
120321
|
+
const decision = classifyExistingAssessmentImport(existing, {
|
|
120322
|
+
purpose: entry2.purpose,
|
|
120323
|
+
...entry2.standard ? { standard: entry2.standard } : {}
|
|
120324
|
+
}, manifest.targetStatus);
|
|
120325
|
+
results[index2] = {
|
|
120326
|
+
...hydratedBase,
|
|
120327
|
+
status: decision.status,
|
|
120328
|
+
association: this.associationImportSummary(existing),
|
|
120329
|
+
message: decision.message
|
|
120330
|
+
};
|
|
120331
|
+
}
|
|
120332
|
+
}
|
|
120333
|
+
}
|
|
120334
|
+
const liveReviewFailures = liveReviewAssessmentImportFailures(manifest.targetStatus, pending.map(({ entry: entry2 }) => entry2), existingRows);
|
|
120335
|
+
for (const { entry: entry2, test } of pending) {
|
|
120336
|
+
const message = liveReviewFailures.get(entry2.qtiTestIdentifier);
|
|
120337
|
+
if (message) {
|
|
120338
|
+
results[entry2.index] = {
|
|
120339
|
+
qtiTestIdentifier: entry2.qtiTestIdentifier,
|
|
120340
|
+
title: test.title,
|
|
120341
|
+
purpose: entry2.purpose,
|
|
120342
|
+
...entry2.standard ? { standard: entry2.standard } : {},
|
|
120343
|
+
sourceSortOrder: entry2.sortOrder,
|
|
120344
|
+
editable: isQtiTestOwnedByGame(test, gameSlug),
|
|
120345
|
+
status: "failed",
|
|
120346
|
+
message
|
|
120347
|
+
};
|
|
120348
|
+
}
|
|
120349
|
+
}
|
|
120350
|
+
const attachablePending = pending.filter(({ entry: entry2 }) => !liveReviewFailures.has(entry2.qtiTestIdentifier));
|
|
120351
|
+
for (const validation of attachablePending) {
|
|
120352
|
+
const { entry: entry2, test } = validation;
|
|
120353
|
+
const { index: index2 } = entry2;
|
|
120354
|
+
const base = {
|
|
120355
|
+
qtiTestIdentifier: entry2.qtiTestIdentifier,
|
|
120356
|
+
title: test.title,
|
|
120357
|
+
purpose: entry2.purpose,
|
|
120358
|
+
...entry2.standard ? { standard: entry2.standard } : {},
|
|
120359
|
+
sourceSortOrder: entry2.sortOrder,
|
|
120360
|
+
editable: isQtiTestOwnedByGame(test, gameSlug)
|
|
120361
|
+
};
|
|
120362
|
+
try {
|
|
120363
|
+
const [row] = await this.deps.db.insert(gameTimebackAssessmentTests).values({
|
|
120364
|
+
integrationId,
|
|
120365
|
+
qtiTestIdentifier: entry2.qtiTestIdentifier,
|
|
120366
|
+
purpose: entry2.purpose,
|
|
120367
|
+
status: manifest.targetStatus,
|
|
120368
|
+
sortOrder: entry2.sortOrder,
|
|
120369
|
+
standardFramework: entry2.standard?.framework,
|
|
120370
|
+
standardIdentifier: entry2.standard?.identifier
|
|
120371
|
+
}).returning();
|
|
120372
|
+
if (!row) {
|
|
120373
|
+
throw new Error("Assessment association create returned no row");
|
|
120374
|
+
}
|
|
120375
|
+
results[index2] = {
|
|
120376
|
+
...base,
|
|
120377
|
+
status: "created",
|
|
120378
|
+
association: this.associationImportSummary(row),
|
|
120379
|
+
message: attachedAssessmentImportMessage(manifest.targetStatus, base.editable)
|
|
120380
|
+
};
|
|
120381
|
+
} catch (error88) {
|
|
120382
|
+
results[index2] = {
|
|
120383
|
+
...base,
|
|
120384
|
+
status: "failed",
|
|
120385
|
+
message: `Association attach failed: ${errorMessage2(error88)}`
|
|
120386
|
+
};
|
|
120387
|
+
if (isUniqueViolation(error88)) {
|
|
120388
|
+
let concurrentAssociation;
|
|
120389
|
+
try {
|
|
120390
|
+
concurrentAssociation = await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
120391
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.qtiTestIdentifier, entry2.qtiTestIdentifier))
|
|
120392
|
+
});
|
|
120393
|
+
} catch {}
|
|
120394
|
+
if (concurrentAssociation) {
|
|
120395
|
+
const decision = classifyExistingAssessmentImport(concurrentAssociation, {
|
|
120396
|
+
purpose: entry2.purpose,
|
|
120397
|
+
...entry2.standard ? { standard: entry2.standard } : {}
|
|
120398
|
+
}, manifest.targetStatus);
|
|
120399
|
+
results[index2] = {
|
|
120400
|
+
...base,
|
|
120401
|
+
status: decision.status,
|
|
120402
|
+
association: this.associationImportSummary(concurrentAssociation),
|
|
120403
|
+
message: decision.message
|
|
120404
|
+
};
|
|
120405
|
+
}
|
|
120406
|
+
}
|
|
120407
|
+
}
|
|
120408
|
+
}
|
|
120409
|
+
setAttribute("app.assessment.operation", "bulk_attach_existing");
|
|
120410
|
+
setAttribute("app.assessment.bulk_attach.target_status", manifest.targetStatus);
|
|
120411
|
+
setAttribute("app.assessment.bulk_attach.created", results.filter((result) => result.status === "created").length);
|
|
120412
|
+
return { results };
|
|
120413
|
+
}
|
|
120163
120414
|
async updateAssessment(integrationId, qtiTestIdentifier, input) {
|
|
120164
120415
|
try {
|
|
120165
120416
|
return await this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx, associations) => {
|
|
@@ -120673,6 +120924,13 @@ class TimebackAssessmentsService {
|
|
|
120673
120924
|
updatedAt: row.updatedAt
|
|
120674
120925
|
};
|
|
120675
120926
|
}
|
|
120927
|
+
associationImportSummary(row) {
|
|
120928
|
+
return {
|
|
120929
|
+
...this.associationSummary(row),
|
|
120930
|
+
createdAt: row.createdAt.toISOString(),
|
|
120931
|
+
updatedAt: row.updatedAt.toISOString()
|
|
120932
|
+
};
|
|
120933
|
+
}
|
|
120676
120934
|
canonicalAssessmentStandard(input) {
|
|
120677
120935
|
const standard = canonicalReviewStandardRef(input);
|
|
120678
120936
|
if (!standard) {
|
|
@@ -120719,6 +120977,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
120719
120977
|
init_qti();
|
|
120720
120978
|
init_timeback3();
|
|
120721
120979
|
init_errors2();
|
|
120980
|
+
init_timeback_assessment_import_util();
|
|
120722
120981
|
init_timeback_assessment_rules_util();
|
|
120723
120982
|
init_timeback_qti_authoring_util();
|
|
120724
120983
|
init_timeback_qti_hydration_util();
|
|
@@ -183335,6 +183594,7 @@ var unenrollStudent;
|
|
|
183335
183594
|
var reactivateEnrollment;
|
|
183336
183595
|
var listAssessments;
|
|
183337
183596
|
var createAssessment;
|
|
183597
|
+
var attachExistingAssessments;
|
|
183338
183598
|
var updateAssessment;
|
|
183339
183599
|
var reorderAssessments;
|
|
183340
183600
|
var reorderQuestions;
|
|
@@ -184001,6 +184261,15 @@ var init_timeback_controller = __esm(() => {
|
|
|
184001
184261
|
qtiTestIdentifier
|
|
184002
184262
|
});
|
|
184003
184263
|
});
|
|
184264
|
+
attachExistingAssessments = requireDeveloper(async (ctx) => {
|
|
184265
|
+
const { gameId, courseId } = ctx.params;
|
|
184266
|
+
if (!gameId || !courseId) {
|
|
184267
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
184268
|
+
}
|
|
184269
|
+
const manifest = await parseRequestBody(ctx.request, AttachExistingAssessmentsRequestSchema);
|
|
184270
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
184271
|
+
return ctx.services.timebackAssessments.attachExistingAssessments(integrationId, manifest);
|
|
184272
|
+
});
|
|
184004
184273
|
updateAssessment = requireDeveloper(async (ctx) => {
|
|
184005
184274
|
const { gameId, courseId, testIdentifier } = ctx.params;
|
|
184006
184275
|
if (!gameId || !courseId || !testIdentifier) {
|
|
@@ -184150,6 +184419,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
184150
184419
|
reactivateEnrollment,
|
|
184151
184420
|
listAssessments,
|
|
184152
184421
|
createAssessment,
|
|
184422
|
+
attachExistingAssessments,
|
|
184153
184423
|
updateAssessment,
|
|
184154
184424
|
reorderAssessments,
|
|
184155
184425
|
removeAssessment,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcademy/vite-plugin",
|
|
3
|
-
"version": "1.2.1-beta.
|
|
3
|
+
"version": "1.2.1-beta.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"archiver": "^7.0.1",
|
|
21
21
|
"picocolors": "^1.1.1",
|
|
22
|
-
"playcademy": "0.28.1-beta.
|
|
22
|
+
"playcademy": "0.28.1-beta.22"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@electric-sql/pglite": "^0.3.16",
|
|
26
26
|
"@inquirer/prompts": "^7.8.6",
|
|
27
27
|
"@playcademy/constants": "0.0.1",
|
|
28
|
-
"@playcademy/sandbox": "0.7.1-beta.
|
|
29
|
-
"@playcademy/sdk": "0.16.1-beta.
|
|
28
|
+
"@playcademy/sandbox": "0.7.1-beta.21",
|
|
29
|
+
"@playcademy/sdk": "0.16.1-beta.21",
|
|
30
30
|
"@playcademy/types": "0.0.1",
|
|
31
31
|
"@playcademy/utils": "0.0.1",
|
|
32
32
|
"@types/archiver": "^6.0.3",
|