latticesql 1.1.2 → 1.2.2
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/cli.js +20 -5
- package/dist/index.cjs +26 -6
- package/dist/index.js +27 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1182,7 +1182,17 @@ var RenderEngine = class {
|
|
|
1182
1182
|
}
|
|
1183
1183
|
for (const entityRow of allRows) {
|
|
1184
1184
|
const slug = def.slug(entityRow);
|
|
1185
|
+
if (/[^a-zA-Z0-9.\-_ @(),#&'+]/.test(slug)) {
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
`Invalid slug "${slug}": contains characters outside the allowed set (alphanumeric, dot, hyphen, underscore, space, @, parens, comma, #, &, ', +)`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1185
1190
|
const entityDir = def.directory ? join5(outputDir, def.directory(entityRow)) : join5(outputDir, directoryRoot, slug);
|
|
1191
|
+
const resolvedDir = resolve3(entityDir);
|
|
1192
|
+
const resolvedBase = resolve3(outputDir);
|
|
1193
|
+
if (!resolvedDir.startsWith(resolvedBase + "/") && resolvedDir !== resolvedBase) {
|
|
1194
|
+
throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
|
|
1195
|
+
}
|
|
1186
1196
|
mkdirSync3(entityDir, { recursive: true });
|
|
1187
1197
|
if (def.attachFileColumn) {
|
|
1188
1198
|
const filePath = entityRow[def.attachFileColumn];
|
|
@@ -1219,8 +1229,10 @@ var RenderEngine = class {
|
|
|
1219
1229
|
counters.skipped++;
|
|
1220
1230
|
}
|
|
1221
1231
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1232
|
+
const fileKeys = Object.keys(def.files);
|
|
1233
|
+
const effectiveCombined = def.combined ?? (fileKeys.length > 1 && renderedFiles.size > 1 ? { outputFile: fileKeys[0] } : void 0);
|
|
1234
|
+
if (effectiveCombined && renderedFiles.size > 0) {
|
|
1235
|
+
const excluded = new Set(effectiveCombined.exclude ?? []);
|
|
1224
1236
|
const parts = [];
|
|
1225
1237
|
for (const filename of Object.keys(def.files)) {
|
|
1226
1238
|
if (!excluded.has(filename) && renderedFiles.has(filename)) {
|
|
@@ -1229,14 +1241,14 @@ var RenderEngine = class {
|
|
|
1229
1241
|
}
|
|
1230
1242
|
if (parts.length > 0) {
|
|
1231
1243
|
const combinedContent = parts.join("\n\n---\n\n");
|
|
1232
|
-
const combinedPath = join5(entityDir,
|
|
1244
|
+
const combinedPath = join5(entityDir, effectiveCombined.outputFile);
|
|
1233
1245
|
if (atomicWrite(combinedPath, combinedContent)) {
|
|
1234
1246
|
filesWritten.push(combinedPath);
|
|
1235
1247
|
} else {
|
|
1236
1248
|
counters.skipped++;
|
|
1237
1249
|
}
|
|
1238
|
-
renderedFiles.set(
|
|
1239
|
-
entityFileHashes[
|
|
1250
|
+
renderedFiles.set(effectiveCombined.outputFile, combinedContent);
|
|
1251
|
+
entityFileHashes[effectiveCombined.outputFile] = { hash: contentHash(combinedContent) };
|
|
1240
1252
|
}
|
|
1241
1253
|
}
|
|
1242
1254
|
manifestEntry.entities[slug] = entityFileHashes;
|
|
@@ -1343,6 +1355,9 @@ var ReverseSyncEngine = class {
|
|
|
1343
1355
|
const pkCols = Object.keys(update.pk);
|
|
1344
1356
|
if (pkCols.length === 0) continue;
|
|
1345
1357
|
const colPattern = /^[a-zA-Z0-9_]+$/;
|
|
1358
|
+
if (!colPattern.test(update.table)) {
|
|
1359
|
+
throw new Error(`Invalid table name in reverse-sync update: ${update.table}`);
|
|
1360
|
+
}
|
|
1346
1361
|
for (const col of [...setCols, ...pkCols]) {
|
|
1347
1362
|
if (!colPattern.test(col)) {
|
|
1348
1363
|
throw new Error(`Invalid column name in reverse-sync update: ${col}`);
|
package/dist/index.cjs
CHANGED
|
@@ -942,7 +942,17 @@ var RenderEngine = class {
|
|
|
942
942
|
}
|
|
943
943
|
for (const entityRow of allRows) {
|
|
944
944
|
const slug = def.slug(entityRow);
|
|
945
|
+
if (/[^a-zA-Z0-9.\-_ @(),#&'+]/.test(slug)) {
|
|
946
|
+
throw new Error(
|
|
947
|
+
`Invalid slug "${slug}": contains characters outside the allowed set (alphanumeric, dot, hyphen, underscore, space, @, parens, comma, #, &, ', +)`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
945
950
|
const entityDir = def.directory ? (0, import_node_path4.join)(outputDir, def.directory(entityRow)) : (0, import_node_path4.join)(outputDir, directoryRoot, slug);
|
|
951
|
+
const resolvedDir = (0, import_node_path4.resolve)(entityDir);
|
|
952
|
+
const resolvedBase = (0, import_node_path4.resolve)(outputDir);
|
|
953
|
+
if (!resolvedDir.startsWith(resolvedBase + "/") && resolvedDir !== resolvedBase) {
|
|
954
|
+
throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
|
|
955
|
+
}
|
|
946
956
|
(0, import_node_fs4.mkdirSync)(entityDir, { recursive: true });
|
|
947
957
|
if (def.attachFileColumn) {
|
|
948
958
|
const filePath = entityRow[def.attachFileColumn];
|
|
@@ -979,8 +989,10 @@ var RenderEngine = class {
|
|
|
979
989
|
counters.skipped++;
|
|
980
990
|
}
|
|
981
991
|
}
|
|
982
|
-
|
|
983
|
-
|
|
992
|
+
const fileKeys = Object.keys(def.files);
|
|
993
|
+
const effectiveCombined = def.combined ?? (fileKeys.length > 1 && renderedFiles.size > 1 ? { outputFile: fileKeys[0] } : void 0);
|
|
994
|
+
if (effectiveCombined && renderedFiles.size > 0) {
|
|
995
|
+
const excluded = new Set(effectiveCombined.exclude ?? []);
|
|
984
996
|
const parts = [];
|
|
985
997
|
for (const filename of Object.keys(def.files)) {
|
|
986
998
|
if (!excluded.has(filename) && renderedFiles.has(filename)) {
|
|
@@ -989,14 +1001,14 @@ var RenderEngine = class {
|
|
|
989
1001
|
}
|
|
990
1002
|
if (parts.length > 0) {
|
|
991
1003
|
const combinedContent = parts.join("\n\n---\n\n");
|
|
992
|
-
const combinedPath = (0, import_node_path4.join)(entityDir,
|
|
1004
|
+
const combinedPath = (0, import_node_path4.join)(entityDir, effectiveCombined.outputFile);
|
|
993
1005
|
if (atomicWrite(combinedPath, combinedContent)) {
|
|
994
1006
|
filesWritten.push(combinedPath);
|
|
995
1007
|
} else {
|
|
996
1008
|
counters.skipped++;
|
|
997
1009
|
}
|
|
998
|
-
renderedFiles.set(
|
|
999
|
-
entityFileHashes[
|
|
1010
|
+
renderedFiles.set(effectiveCombined.outputFile, combinedContent);
|
|
1011
|
+
entityFileHashes[effectiveCombined.outputFile] = { hash: contentHash(combinedContent) };
|
|
1000
1012
|
}
|
|
1001
1013
|
}
|
|
1002
1014
|
manifestEntry.entities[slug] = entityFileHashes;
|
|
@@ -1103,6 +1115,9 @@ var ReverseSyncEngine = class {
|
|
|
1103
1115
|
const pkCols = Object.keys(update.pk);
|
|
1104
1116
|
if (pkCols.length === 0) continue;
|
|
1105
1117
|
const colPattern = /^[a-zA-Z0-9_]+$/;
|
|
1118
|
+
if (!colPattern.test(update.table)) {
|
|
1119
|
+
throw new Error(`Invalid table name in reverse-sync update: ${update.table}`);
|
|
1120
|
+
}
|
|
1106
1121
|
for (const col of [...setCols, ...pkCols]) {
|
|
1107
1122
|
if (!colPattern.test(col)) {
|
|
1108
1123
|
throw new Error(`Invalid column name in reverse-sync update: ${col}`);
|
|
@@ -3154,9 +3169,14 @@ async function autoUpdate(opts) {
|
|
|
3154
3169
|
if (!installed) return result;
|
|
3155
3170
|
const latest = await getLatestVersion("latticesql");
|
|
3156
3171
|
if (!latest || !isNewer(latest, installed)) return result;
|
|
3172
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
3173
|
+
if (!SEMVER_RE.test(latest)) {
|
|
3174
|
+
console.error(`[latticesql] Rejecting invalid version: "${latest}"`);
|
|
3175
|
+
return result;
|
|
3176
|
+
}
|
|
3157
3177
|
log(`[latticesql] Updating: latticesql@${installed} \u2192 ${latest}`);
|
|
3158
3178
|
try {
|
|
3159
|
-
(0, import_node_child_process.
|
|
3179
|
+
(0, import_node_child_process.execFileSync)("npm", ["install", `latticesql@${latest}`], {
|
|
3160
3180
|
cwd: process.cwd(),
|
|
3161
3181
|
stdio: opts?.quiet ? "ignore" : "inherit",
|
|
3162
3182
|
timeout: 6e4
|
package/dist/index.js
CHANGED
|
@@ -874,7 +874,17 @@ var RenderEngine = class {
|
|
|
874
874
|
}
|
|
875
875
|
for (const entityRow of allRows) {
|
|
876
876
|
const slug = def.slug(entityRow);
|
|
877
|
+
if (/[^a-zA-Z0-9.\-_ @(),#&'+]/.test(slug)) {
|
|
878
|
+
throw new Error(
|
|
879
|
+
`Invalid slug "${slug}": contains characters outside the allowed set (alphanumeric, dot, hyphen, underscore, space, @, parens, comma, #, &, ', +)`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
877
882
|
const entityDir = def.directory ? join4(outputDir, def.directory(entityRow)) : join4(outputDir, directoryRoot, slug);
|
|
883
|
+
const resolvedDir = resolve(entityDir);
|
|
884
|
+
const resolvedBase = resolve(outputDir);
|
|
885
|
+
if (!resolvedDir.startsWith(resolvedBase + "/") && resolvedDir !== resolvedBase) {
|
|
886
|
+
throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
|
|
887
|
+
}
|
|
878
888
|
mkdirSync2(entityDir, { recursive: true });
|
|
879
889
|
if (def.attachFileColumn) {
|
|
880
890
|
const filePath = entityRow[def.attachFileColumn];
|
|
@@ -911,8 +921,10 @@ var RenderEngine = class {
|
|
|
911
921
|
counters.skipped++;
|
|
912
922
|
}
|
|
913
923
|
}
|
|
914
|
-
|
|
915
|
-
|
|
924
|
+
const fileKeys = Object.keys(def.files);
|
|
925
|
+
const effectiveCombined = def.combined ?? (fileKeys.length > 1 && renderedFiles.size > 1 ? { outputFile: fileKeys[0] } : void 0);
|
|
926
|
+
if (effectiveCombined && renderedFiles.size > 0) {
|
|
927
|
+
const excluded = new Set(effectiveCombined.exclude ?? []);
|
|
916
928
|
const parts = [];
|
|
917
929
|
for (const filename of Object.keys(def.files)) {
|
|
918
930
|
if (!excluded.has(filename) && renderedFiles.has(filename)) {
|
|
@@ -921,14 +933,14 @@ var RenderEngine = class {
|
|
|
921
933
|
}
|
|
922
934
|
if (parts.length > 0) {
|
|
923
935
|
const combinedContent = parts.join("\n\n---\n\n");
|
|
924
|
-
const combinedPath = join4(entityDir,
|
|
936
|
+
const combinedPath = join4(entityDir, effectiveCombined.outputFile);
|
|
925
937
|
if (atomicWrite(combinedPath, combinedContent)) {
|
|
926
938
|
filesWritten.push(combinedPath);
|
|
927
939
|
} else {
|
|
928
940
|
counters.skipped++;
|
|
929
941
|
}
|
|
930
|
-
renderedFiles.set(
|
|
931
|
-
entityFileHashes[
|
|
942
|
+
renderedFiles.set(effectiveCombined.outputFile, combinedContent);
|
|
943
|
+
entityFileHashes[effectiveCombined.outputFile] = { hash: contentHash(combinedContent) };
|
|
932
944
|
}
|
|
933
945
|
}
|
|
934
946
|
manifestEntry.entities[slug] = entityFileHashes;
|
|
@@ -1035,6 +1047,9 @@ var ReverseSyncEngine = class {
|
|
|
1035
1047
|
const pkCols = Object.keys(update.pk);
|
|
1036
1048
|
if (pkCols.length === 0) continue;
|
|
1037
1049
|
const colPattern = /^[a-zA-Z0-9_]+$/;
|
|
1050
|
+
if (!colPattern.test(update.table)) {
|
|
1051
|
+
throw new Error(`Invalid table name in reverse-sync update: ${update.table}`);
|
|
1052
|
+
}
|
|
1038
1053
|
for (const col of [...setCols, ...pkCols]) {
|
|
1039
1054
|
if (!colPattern.test(col)) {
|
|
1040
1055
|
throw new Error(`Invalid column name in reverse-sync update: ${col}`);
|
|
@@ -3044,7 +3059,7 @@ function applyWriteEntry(db, entry) {
|
|
|
3044
3059
|
}
|
|
3045
3060
|
|
|
3046
3061
|
// src/auto-update.ts
|
|
3047
|
-
import {
|
|
3062
|
+
import { execFileSync } from "child_process";
|
|
3048
3063
|
import { readFileSync as readFileSync6 } from "fs";
|
|
3049
3064
|
import { join as join7 } from "path";
|
|
3050
3065
|
function getInstalledVersion(pkgName) {
|
|
@@ -3086,9 +3101,14 @@ async function autoUpdate(opts) {
|
|
|
3086
3101
|
if (!installed) return result;
|
|
3087
3102
|
const latest = await getLatestVersion("latticesql");
|
|
3088
3103
|
if (!latest || !isNewer(latest, installed)) return result;
|
|
3104
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
3105
|
+
if (!SEMVER_RE.test(latest)) {
|
|
3106
|
+
console.error(`[latticesql] Rejecting invalid version: "${latest}"`);
|
|
3107
|
+
return result;
|
|
3108
|
+
}
|
|
3089
3109
|
log(`[latticesql] Updating: latticesql@${installed} \u2192 ${latest}`);
|
|
3090
3110
|
try {
|
|
3091
|
-
|
|
3111
|
+
execFileSync("npm", ["install", `latticesql@${latest}`], {
|
|
3092
3112
|
cwd: process.cwd(),
|
|
3093
3113
|
stdio: opts?.quiet ? "ignore" : "inherit",
|
|
3094
3114
|
timeout: 6e4
|