pairit 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +140 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9806,10 +9806,131 @@ async function attachHtmlToCompiledConfig(configPath, compiled) {
|
|
|
9806
9806
|
}
|
|
9807
9807
|
return sources;
|
|
9808
9808
|
}
|
|
9809
|
+
function listWorkspaceComponents(config) {
|
|
9810
|
+
const pages = config.pages;
|
|
9811
|
+
const pageList = Array.isArray(pages) ? pages.filter(isRecord) : isRecord(pages) ? Object.values(pages).filter(isRecord) : [];
|
|
9812
|
+
const result = [];
|
|
9813
|
+
for (const page of pageList) {
|
|
9814
|
+
if (!Array.isArray(page.components))
|
|
9815
|
+
continue;
|
|
9816
|
+
for (const comp of page.components) {
|
|
9817
|
+
if (!isRecord(comp) || comp.type !== "live-workspace")
|
|
9818
|
+
continue;
|
|
9819
|
+
const props = isRecord(comp.props) ? comp.props : {};
|
|
9820
|
+
const id = typeof comp.id === "string" ? comp.id : "live-workspace";
|
|
9821
|
+
result.push({ id, props });
|
|
9822
|
+
}
|
|
9823
|
+
}
|
|
9824
|
+
return result;
|
|
9825
|
+
}
|
|
9826
|
+
async function resolveWorkspaceSchema(schema, componentId, configDir) {
|
|
9827
|
+
if (typeof schema !== "string" || !schema.trim().endsWith(".json")) {
|
|
9828
|
+
throw new Error(`live-workspace ${componentId}: schema must be a .json file`);
|
|
9829
|
+
}
|
|
9830
|
+
const absolutePath = path2.resolve(configDir, schema.trim());
|
|
9831
|
+
const rel = path2.relative(configDir, absolutePath);
|
|
9832
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
9833
|
+
throw new Error(`live-workspace ${componentId}: schema must stay inside the config directory`);
|
|
9834
|
+
}
|
|
9835
|
+
try {
|
|
9836
|
+
JSON.parse(await readFile2(absolutePath, "utf8"));
|
|
9837
|
+
} catch (error) {
|
|
9838
|
+
throw new Error(`live-workspace ${componentId}: can't read schema ${schema}: ${error.message}`);
|
|
9839
|
+
}
|
|
9840
|
+
return absolutePath;
|
|
9841
|
+
}
|
|
9842
|
+
async function inspectWorkspaceFiles(configPath, config) {
|
|
9843
|
+
const configDir = await resolveConfigDir(configPath);
|
|
9844
|
+
const errors = [];
|
|
9845
|
+
for (const comp of listWorkspaceComponents(config)) {
|
|
9846
|
+
try {
|
|
9847
|
+
if (comp.props.renderer !== undefined) {
|
|
9848
|
+
await resolveHtmlSource(comp.props.renderer, comp.id, configDir);
|
|
9849
|
+
}
|
|
9850
|
+
if (comp.props.schema !== undefined) {
|
|
9851
|
+
await resolveWorkspaceSchema(comp.props.schema, comp.id, configDir);
|
|
9852
|
+
}
|
|
9853
|
+
} catch (error) {
|
|
9854
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
if (errors.length)
|
|
9858
|
+
throw new Error(errors.join(`
|
|
9859
|
+
`));
|
|
9860
|
+
}
|
|
9861
|
+
async function attachWorkspaceFilesToCompiledConfig(configPath, compiled) {
|
|
9862
|
+
const configDir = await resolveConfigDir(configPath);
|
|
9863
|
+
for (const comp of listWorkspaceComponents(compiled)) {
|
|
9864
|
+
if (comp.props.renderer !== undefined) {
|
|
9865
|
+
const resolved = await resolveHtmlSource(comp.props.renderer, comp.id, configDir);
|
|
9866
|
+
comp.props.rendererHtml = await readFile2(resolved.absolutePath, "utf8");
|
|
9867
|
+
}
|
|
9868
|
+
if (comp.props.schema !== undefined) {
|
|
9869
|
+
const schemaPath = await resolveWorkspaceSchema(comp.props.schema, comp.id, configDir);
|
|
9870
|
+
comp.props.schema = JSON.parse(await readFile2(schemaPath, "utf8"));
|
|
9871
|
+
}
|
|
9872
|
+
}
|
|
9873
|
+
}
|
|
9874
|
+
|
|
9875
|
+
// src/randomize-lint.ts
|
|
9876
|
+
function lintRandomization(config) {
|
|
9877
|
+
const errors = [];
|
|
9878
|
+
const groupSize = singleGroupSize(config);
|
|
9879
|
+
const check = (where, settings) => {
|
|
9880
|
+
if (settings.distribute !== true)
|
|
9881
|
+
return;
|
|
9882
|
+
if (settings.scope !== "group") {
|
|
9883
|
+
errors.push(`${where}: distribute: true needs scope: group`);
|
|
9884
|
+
}
|
|
9885
|
+
const conditions = settings.conditions;
|
|
9886
|
+
if (!Array.isArray(conditions) || conditions.length === 0) {
|
|
9887
|
+
errors.push(`${where}: distribute: true needs a conditions list`);
|
|
9888
|
+
return;
|
|
9889
|
+
}
|
|
9890
|
+
if (groupSize !== undefined && conditions.length !== groupSize) {
|
|
9891
|
+
errors.push(`${where}: distribute: true needs one condition per group member (${conditions.length} conditions, groups of ${groupSize})`);
|
|
9892
|
+
}
|
|
9893
|
+
};
|
|
9894
|
+
config.pages?.forEach((page, pageIndex) => {
|
|
9895
|
+
const pageName = page.id ?? `pages[${pageIndex}]`;
|
|
9896
|
+
page.onEnter?.forEach((action, actionIndex) => {
|
|
9897
|
+
const a = action;
|
|
9898
|
+
if (a?.type === "randomize") {
|
|
9899
|
+
check(`${pageName}.onEnter[${actionIndex}]`, a);
|
|
9900
|
+
}
|
|
9901
|
+
});
|
|
9902
|
+
page.components?.forEach((component, componentIndex) => {
|
|
9903
|
+
const c = component;
|
|
9904
|
+
if (c?.type === "randomization" && c.props) {
|
|
9905
|
+
check(`${pageName}.components[${componentIndex}]`, c.props);
|
|
9906
|
+
}
|
|
9907
|
+
});
|
|
9908
|
+
});
|
|
9909
|
+
return errors;
|
|
9910
|
+
}
|
|
9911
|
+
function singleGroupSize(config) {
|
|
9912
|
+
const sizes = new Set;
|
|
9913
|
+
if (Array.isArray(config.matchmaking)) {
|
|
9914
|
+
for (const pool of config.matchmaking) {
|
|
9915
|
+
const size = pool?.num_users;
|
|
9916
|
+
sizes.add(typeof size === "number" ? size : 2);
|
|
9917
|
+
}
|
|
9918
|
+
}
|
|
9919
|
+
for (const page of config.pages ?? []) {
|
|
9920
|
+
for (const component of page.components ?? []) {
|
|
9921
|
+
const c = component;
|
|
9922
|
+
if (c?.type !== "matchmaking" || c.props?.poolId !== undefined)
|
|
9923
|
+
continue;
|
|
9924
|
+
const size = c.props?.num_users;
|
|
9925
|
+
sizes.add(typeof size === "number" ? size : 2);
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
return sizes.size === 1 ? [...sizes][0] : undefined;
|
|
9929
|
+
}
|
|
9809
9930
|
|
|
9810
9931
|
// src/index.ts
|
|
9811
9932
|
var program2 = new Command;
|
|
9812
|
-
program2.name("pairit").description("CLI for Pairit experiment configs").version("0.3.
|
|
9933
|
+
program2.name("pairit").description("CLI for Pairit experiment configs").version("0.3.3").option("--env <name>", "Target environment: staging or production", (value) => {
|
|
9813
9934
|
try {
|
|
9814
9935
|
return parseEnvName(value);
|
|
9815
9936
|
} catch (error) {
|
|
@@ -10101,9 +10222,11 @@ async function lintConfig(configPath) {
|
|
|
10101
10222
|
});
|
|
10102
10223
|
});
|
|
10103
10224
|
}
|
|
10225
|
+
errors.push(...lintRandomization(config));
|
|
10104
10226
|
if (errors.length) {
|
|
10105
10227
|
throw new Error(errors.join(", "));
|
|
10106
10228
|
}
|
|
10229
|
+
await inspectWorkspaceFiles(configPath, config);
|
|
10107
10230
|
return inspectHtmlSources(configPath, config);
|
|
10108
10231
|
}
|
|
10109
10232
|
async function compileConfig(configPath) {
|
|
@@ -10162,12 +10285,17 @@ async function compileConfig(configPath) {
|
|
|
10162
10285
|
}
|
|
10163
10286
|
async function buildUploadPayload(configPath, options) {
|
|
10164
10287
|
const sourceConfig = await loadConfig(configPath);
|
|
10288
|
+
const randomizationErrors = lintRandomization(sourceConfig);
|
|
10289
|
+
if (randomizationErrors.length) {
|
|
10290
|
+
throw new Error(randomizationErrors.join(", "));
|
|
10291
|
+
}
|
|
10165
10292
|
const allowRetake = sourceConfig.allowRetake === true;
|
|
10166
10293
|
const requireAuth = sourceConfig.requireAuth;
|
|
10167
10294
|
const compiledPath = await compileConfig(configPath);
|
|
10168
10295
|
const compiledContent = await readFile3(compiledPath, "utf8");
|
|
10169
10296
|
const parsed = JSON.parse(compiledContent);
|
|
10170
10297
|
const attachments = await attachHtmlToCompiledConfig(configPath, parsed);
|
|
10298
|
+
await attachWorkspaceFilesToCompiledConfig(configPath, parsed);
|
|
10171
10299
|
const hashBuffer = createHash("sha256").update(JSON.stringify(parsed)).digest();
|
|
10172
10300
|
const checksum = hashBuffer.toString("hex");
|
|
10173
10301
|
const explicitName = options.name ?? sourceConfig.name;
|
|
@@ -10357,6 +10485,7 @@ async function exportData(configId, options) {
|
|
|
10357
10485
|
await fetchAndWriteExport(`/data/${cfg}/groups`, path3.join(outDir, `${configId}-groups`), (r) => r.groups ?? [], format, (group) => group, "groups");
|
|
10358
10486
|
await fetchAndWriteExport(`/data/${cfg}/survey-responses`, path3.join(outDir, `${configId}-survey-responses`), (r) => r.surveyResponses ?? [], format, flattenSurveyResponse, "survey responses");
|
|
10359
10487
|
await fetchAndWriteExport(`/data/${cfg}/workspace-documents`, path3.join(outDir, `${configId}-workspace-documents`), (r) => r.workspaceDocuments ?? [], format, flattenWorkspaceDocument, "workspace documents");
|
|
10488
|
+
await fetchAndWriteExport(`/data/${cfg}/workspace-updates`, path3.join(outDir, `${configId}-workspace-updates`), (r) => r.workspaceUpdates ?? [], format, (update) => update, "workspace updates");
|
|
10360
10489
|
console.log(`
|
|
10361
10490
|
Files written to: ${outDir}`);
|
|
10362
10491
|
}
|
|
@@ -10411,13 +10540,21 @@ function flattenSurveyResponse(response) {
|
|
|
10411
10540
|
return flattened;
|
|
10412
10541
|
}
|
|
10413
10542
|
function flattenWorkspaceDocument(doc) {
|
|
10414
|
-
const { fields, ...rest } = doc;
|
|
10415
|
-
const flattened = {
|
|
10543
|
+
const { fields, authorship, fieldAuthors, ...rest } = doc;
|
|
10544
|
+
const flattened = {
|
|
10545
|
+
...rest,
|
|
10546
|
+
authorship: serializeValue(authorship)
|
|
10547
|
+
};
|
|
10416
10548
|
if (fields && typeof fields === "object") {
|
|
10417
10549
|
for (const [key, value] of Object.entries(fields)) {
|
|
10418
10550
|
flattened[`fields.${key}`] = serializeValue(value);
|
|
10419
10551
|
}
|
|
10420
10552
|
}
|
|
10553
|
+
if (fieldAuthors && typeof fieldAuthors === "object") {
|
|
10554
|
+
for (const [key, origin] of Object.entries(fieldAuthors)) {
|
|
10555
|
+
flattened[`fieldAuthors.${key}`] = origin;
|
|
10556
|
+
}
|
|
10557
|
+
}
|
|
10421
10558
|
return flattened;
|
|
10422
10559
|
}
|
|
10423
10560
|
function serializeValue(value) {
|