@lunora/config 1.0.0-alpha.80 → 1.0.0-alpha.82
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.mjs +1 -30
- package/dist/packem_shared/ACCENT-CLeV5v0K.mjs +7 -0
- package/dist/packem_shared/AGENT_MODE_ENV-B54hVQ_w.mjs +1 -0
- package/dist/packem_shared/AGENT_RULES_DIR-hP9TiDNx.mjs +1 -0
- package/dist/packem_shared/DEV_DAEMON_ENV-D9Z83rlU.mjs +4 -0
- package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-DX6xGbpr.mjs +1 -0
- package/dist/packem_shared/LINKED_PROJECT_DIR-CMzUvj-1.mjs +2 -0
- package/dist/packem_shared/LUNORA_CONFIG_FILE-3bpx6TZN.mjs +1 -0
- package/dist/packem_shared/LUNORA_EVENT_SOURCE-ZclWASXS.mjs +1 -0
- package/dist/packem_shared/LunoraReporter-BnXUqh8t.mjs +6 -0
- package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-BgmvEPA-.mjs +1 -0
- package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CUYWD0Mh.mjs +1 -0
- package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-ws6iE0y-.mjs +1 -0
- package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-DPcRuAhj.mjs +1 -0
- package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-CZ8rSh2k.mjs +1 -0
- package/dist/packem_shared/SEED_ENDPOINT-BaYShU8q.mjs +1 -0
- package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-B1h_wNDN.mjs +1 -0
- package/dist/packem_shared/WRANGLER_FILES-Bi_18Pj6.mjs +1 -0
- package/dist/packem_shared/applyAdditiveEdit-BdcnSa-j.mjs +6 -0
- package/dist/packem_shared/assetContentType-DNZyUOuF.mjs +1 -0
- package/dist/packem_shared/buildPackageSecretsBlock-BpvaZzQ8.mjs +18 -0
- package/dist/packem_shared/classifyPolicyEdit-BzC_MfYK.mjs +24 -0
- package/dist/packem_shared/collectWranglerSecretVariables-OacEwPmL.mjs +1 -0
- package/dist/packem_shared/createConfirm-7IL0kZyE.mjs +7 -0
- package/dist/packem_shared/detectFramework-VTQfCNXy.mjs +1 -0
- package/dist/packem_shared/discoverAgentInfo-BJm0QtoI.mjs +1 -0
- package/dist/packem_shared/discoverContainerInfo-CYG9j2LY.mjs +1 -0
- package/dist/packem_shared/discoverSchemaInfo-B2W3VrcT.mjs +1 -0
- package/dist/packem_shared/discoverWorkflowInfo-Bbc0u2gE.mjs +1 -0
- package/dist/packem_shared/inferLunoraBindings-DWSabh1j.mjs +1 -0
- package/dist/packem_shared/jsonc-edit-BZVpxVA0.mjs +1 -0
- package/dist/packem_shared/parseDevVariable-NAVJ8tgA.mjs +1 -0
- package/dist/packem_shared/parseSchema-BPmtMgmb.mjs +1 -0
- package/dist/packem_shared/reconcileWranglerBindings-Dd3tLauZ.mjs +1 -0
- package/dist/packem_shared/reconcileWranglerCompatibilityDate-BXNiEQ7p.mjs +1 -0
- package/dist/packem_shared/reconcileWranglerCrons-BmQa_kGL.mjs +1 -0
- package/dist/packem_shared/renderStudioHtml-B2AkxcW1.mjs +15 -0
- package/dist/packem_shared/serveJsonHandler-C5GWlWJF.mjs +1 -0
- package/dist/packem_shared/streamContainerLogs-BPYBNrWS.mjs +2 -0
- package/dist/packem_shared/write-atomic-Cdr2oQb-.mjs +1 -0
- package/dist/studio-host/index.mjs +1 -7
- package/package.json +3 -3
- package/dist/packem_shared/ACCENT-DW1XJn8i.mjs +0 -40
- package/dist/packem_shared/AGENT_MODE_ENV-B5R7jpY7.mjs +0 -17
- package/dist/packem_shared/AGENT_RULES_DIR-lcgC08aE.mjs +0 -40
- package/dist/packem_shared/DEV_DAEMON_ENV-FIEtW7Fz.mjs +0 -163
- package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs +0 -37
- package/dist/packem_shared/LINKED_PROJECT_DIR-CXwXzV_C.mjs +0 -52
- package/dist/packem_shared/LUNORA_CONFIG_FILE-CtcIcB5-.mjs +0 -34
- package/dist/packem_shared/LUNORA_EVENT_SOURCE-M1KM9_pR.mjs +0 -112
- package/dist/packem_shared/LunoraReporter-Ci-bDCK9.mjs +0 -70
- package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-NCTSphDk.mjs +0 -138
- package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CvTZpaZF.mjs +0 -100
- package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-C-F_Flgy.mjs +0 -100
- package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-DP0SFVy9.mjs +0 -607
- package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-Bt2SNkpV.mjs +0 -95
- package/dist/packem_shared/SEED_ENDPOINT-7izz_Jao.mjs +0 -61
- package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-CaXbS7CX.mjs +0 -11
- package/dist/packem_shared/WRANGLER_FILES-DwSuC-Kn.mjs +0 -25
- package/dist/packem_shared/applyAdditiveEdit-C-snTFEV.mjs +0 -228
- package/dist/packem_shared/assetContentType-ByZjeV3D.mjs +0 -72
- package/dist/packem_shared/buildPackageSecretsBlock-BxOcyaAY.mjs +0 -281
- package/dist/packem_shared/classifyPolicyEdit-BHeAqF8P.mjs +0 -99
- package/dist/packem_shared/collectWranglerSecretVariables-BRWIvT-e.mjs +0 -56
- package/dist/packem_shared/createConfirm-BMikeJC9.mjs +0 -117
- package/dist/packem_shared/detectFramework-U08038Yp.mjs +0 -41
- package/dist/packem_shared/discoverAgentInfo-eXA3hF4v.mjs +0 -19
- package/dist/packem_shared/discoverContainerInfo-BXFs6Wav.mjs +0 -19
- package/dist/packem_shared/discoverSchemaInfo-BB-CKlTK.mjs +0 -25
- package/dist/packem_shared/discoverWorkflowInfo-CedvR0mn.mjs +0 -19
- package/dist/packem_shared/inferLunoraBindings-BcvvtpSG.mjs +0 -397
- package/dist/packem_shared/jsonc-edit-SbcD7MAe.mjs +0 -9
- package/dist/packem_shared/parseDevVariable-B0FEvfHC.mjs +0 -21
- package/dist/packem_shared/parseSchema-DSeyktvG.mjs +0 -107
- package/dist/packem_shared/reconcileWranglerBindings-2Cww5VLa.mjs +0 -362
- package/dist/packem_shared/reconcileWranglerCompatibilityDate-C8c497ag.mjs +0 -31
- package/dist/packem_shared/reconcileWranglerCrons-DvsceVFz.mjs +0 -29
- package/dist/packem_shared/renderStudioHtml-449Ysn75.mjs +0 -37
- package/dist/packem_shared/serveJsonHandler-Cl2Wqkv8.mjs +0 -86
- package/dist/packem_shared/streamContainerLogs-BZ4cOZwH.mjs +0 -157
- package/dist/packem_shared/write-atomic-B2REqapi.mjs +0 -14
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { runCodegen, CodegenDiagnosticError } from '@lunora/codegen';
|
|
3
|
-
import { classifyEdit, applyAdditiveEdit } from './applyAdditiveEdit-C-snTFEV.mjs';
|
|
4
|
-
import { parseSchema } from './parseSchema-DSeyktvG.mjs';
|
|
5
|
-
import { w as writeFileAtomic } from './write-atomic-B2REqapi.mjs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
|
|
8
|
-
const SCHEMA_EDIT_ENDPOINT = "/__lunora/schema-edit";
|
|
9
|
-
const statusForFailure = (reason) => {
|
|
10
|
-
if (reason === "destructive") {
|
|
11
|
-
return 409;
|
|
12
|
-
}
|
|
13
|
-
if (reason === "duplicate-table" || reason === "duplicate-column" || reason === "duplicate-index") {
|
|
14
|
-
return 409;
|
|
15
|
-
}
|
|
16
|
-
if (reason === "unknown-table") {
|
|
17
|
-
return 404;
|
|
18
|
-
}
|
|
19
|
-
if (reason === "invalid-identifier" || reason === "invalid-validator") {
|
|
20
|
-
return 400;
|
|
21
|
-
}
|
|
22
|
-
return 422;
|
|
23
|
-
};
|
|
24
|
-
const parseFailureResponse = (result) => {
|
|
25
|
-
return {
|
|
26
|
-
body: { error: result.reason, ok: false },
|
|
27
|
-
status: 422
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
const readSchema = (schemaPath) => {
|
|
31
|
-
if (!existsSync(schemaPath)) {
|
|
32
|
-
return { response: { body: { error: "no-schema-file", ok: false }, status: 404 } };
|
|
33
|
-
}
|
|
34
|
-
const parsed = parseSchema(readFileSync(schemaPath, "utf8"));
|
|
35
|
-
if (!parsed.ok) {
|
|
36
|
-
return { response: parseFailureResponse(parsed) };
|
|
37
|
-
}
|
|
38
|
-
return { tables: parsed.tables };
|
|
39
|
-
};
|
|
40
|
-
const needsMigrationResponse = (edit) => {
|
|
41
|
-
return {
|
|
42
|
-
body: {
|
|
43
|
-
edit,
|
|
44
|
-
message: "This edit changes stored data and must go through a migration. Review the migration before applying.",
|
|
45
|
-
needsMigration: true,
|
|
46
|
-
ok: false
|
|
47
|
-
},
|
|
48
|
-
status: 409
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
const handlePost = (request, schemaPath) => {
|
|
52
|
-
const edit = request.body;
|
|
53
|
-
if (edit === void 0 || edit === null || typeof edit !== "object" || typeof edit.kind !== "string") {
|
|
54
|
-
return { body: { error: "invalid-edit", ok: false }, status: 400 };
|
|
55
|
-
}
|
|
56
|
-
if (classifyEdit(edit) === "destructive") {
|
|
57
|
-
return needsMigrationResponse(edit);
|
|
58
|
-
}
|
|
59
|
-
if (!existsSync(schemaPath)) {
|
|
60
|
-
return { body: { error: "no-schema-file", ok: false }, status: 404 };
|
|
61
|
-
}
|
|
62
|
-
const applied = applyAdditiveEdit(readFileSync(schemaPath, "utf8"), edit);
|
|
63
|
-
if (!applied.ok) {
|
|
64
|
-
return { body: { error: applied.reason, ok: false }, status: statusForFailure(applied.reason) };
|
|
65
|
-
}
|
|
66
|
-
writeFileAtomic(schemaPath, applied.text);
|
|
67
|
-
let diagnostics = [];
|
|
68
|
-
try {
|
|
69
|
-
runCodegen({ lunoraDirectory: request.schemaDirectory ?? "lunora", projectRoot: request.projectRoot });
|
|
70
|
-
} catch (error) {
|
|
71
|
-
if (error instanceof CodegenDiagnosticError) {
|
|
72
|
-
diagnostics = [error.message];
|
|
73
|
-
} else {
|
|
74
|
-
return { body: { error: error instanceof Error ? error.message : String(error), ok: false }, status: 500 };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const parsed = parseSchema(applied.text);
|
|
78
|
-
return {
|
|
79
|
-
body: { diagnostics, ok: true, tables: parsed.ok ? parsed.tables : [] },
|
|
80
|
-
status: 200
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
const handleSchemaEditRequest = (request) => {
|
|
84
|
-
const schemaPath = join(request.projectRoot, request.schemaDirectory ?? "lunora", "schema.ts");
|
|
85
|
-
if (request.method === "GET") {
|
|
86
|
-
const read = readSchema(schemaPath);
|
|
87
|
-
return "response" in read ? read.response : { body: { ok: true, tables: read.tables }, status: 200 };
|
|
88
|
-
}
|
|
89
|
-
if (request.method === "POST") {
|
|
90
|
-
return handlePost(request, schemaPath);
|
|
91
|
-
}
|
|
92
|
-
return { body: { error: "method-not-allowed", ok: false }, status: 405 };
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export { SCHEMA_EDIT_ENDPOINT, handleSchemaEditRequest };
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { discoverSchema, schemaFromIr } from '@lunora/codegen';
|
|
3
|
-
import { seedPlan } from '@lunora/seed';
|
|
4
|
-
import { Project } from 'ts-morph';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
|
|
7
|
-
const SEED_ENDPOINT = "/__lunora/seed";
|
|
8
|
-
const MAX_SEED_ROWS = 1e3;
|
|
9
|
-
const jsonReplacer = (_key, value) => {
|
|
10
|
-
if (typeof value === "bigint") {
|
|
11
|
-
return Number(value);
|
|
12
|
-
}
|
|
13
|
-
if (value instanceof ArrayBuffer) {
|
|
14
|
-
return [...new Uint8Array(value)];
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
};
|
|
18
|
-
const clampCount = (count) => {
|
|
19
|
-
if (typeof count !== "number" || !Number.isFinite(count)) {
|
|
20
|
-
return 10;
|
|
21
|
-
}
|
|
22
|
-
return Math.min(Math.max(1, Math.floor(count)), MAX_SEED_ROWS);
|
|
23
|
-
};
|
|
24
|
-
const handleSeedRequest = (request) => {
|
|
25
|
-
if (request.method !== "POST") {
|
|
26
|
-
return { body: { error: "method-not-allowed", ok: false }, status: 405 };
|
|
27
|
-
}
|
|
28
|
-
const { body } = request;
|
|
29
|
-
if (body === void 0 || body === null || typeof body !== "object") {
|
|
30
|
-
return { body: { error: "invalid-request", ok: false }, status: 400 };
|
|
31
|
-
}
|
|
32
|
-
const { count, existingIds, seed, table } = body;
|
|
33
|
-
if (typeof table !== "string" || table.length === 0) {
|
|
34
|
-
return { body: { error: "missing-table", ok: false }, status: 400 };
|
|
35
|
-
}
|
|
36
|
-
const lunoraDirectory = request.schemaDirectory ?? "lunora";
|
|
37
|
-
const schemaPath = join(request.projectRoot, lunoraDirectory, "schema.ts");
|
|
38
|
-
if (!existsSync(schemaPath)) {
|
|
39
|
-
return { body: { error: "schema-not-found", ok: false }, status: 404 };
|
|
40
|
-
}
|
|
41
|
-
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
42
|
-
const ir = discoverSchema(project, schemaPath);
|
|
43
|
-
if (!ir.tables.some((candidate) => candidate.name === table)) {
|
|
44
|
-
return { body: { error: "unknown-table", ok: false, table }, status: 404 };
|
|
45
|
-
}
|
|
46
|
-
const schema = schemaFromIr(ir);
|
|
47
|
-
const plan = seedPlan(schema, {
|
|
48
|
-
// Pass every sampled parent table as `existingIds` so the generator links
|
|
49
|
-
// to live rows and never fabricates parents — matching the studio's
|
|
50
|
-
// "existing rows are not affected, FKs point at what's there" semantics.
|
|
51
|
-
existingIds: existingIds ?? {},
|
|
52
|
-
defaultCount: clampCount(count),
|
|
53
|
-
only: [table],
|
|
54
|
-
seed: seed ?? 0
|
|
55
|
-
});
|
|
56
|
-
const rows = plan.find((entry) => entry.table === table)?.rows ?? [];
|
|
57
|
-
const jsonSafeRows = JSON.parse(JSON.stringify(rows, jsonReplacer));
|
|
58
|
-
return { body: { ok: true, rows: jsonSafeRows }, status: 200 };
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export { SEED_ENDPOINT, handleSeedRequest };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const WORKERS_CACHE_MIN_DATE = "2026-05-01";
|
|
2
|
-
const isCacheEnabled = (parsed) => {
|
|
3
|
-
if (!parsed) {
|
|
4
|
-
return false;
|
|
5
|
-
}
|
|
6
|
-
const topLevel = typeof parsed.cache === "object" && parsed.cache !== null && parsed.cache.enabled === true;
|
|
7
|
-
const inExports = typeof parsed.exports === "object" && parsed.exports !== null && Object.values(parsed.exports).some((entry) => typeof entry === "object" && entry !== null && entry.cache?.enabled === true);
|
|
8
|
-
return topLevel || inExports;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export { WORKERS_CACHE_MIN_DATE, isCacheEnabled };
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { parse } from 'jsonc-parser';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
const WRANGLER_FILES = ["wrangler.jsonc", "wrangler.json"];
|
|
6
|
-
const findWranglerFile = (projectRoot) => {
|
|
7
|
-
for (const candidate of WRANGLER_FILES) {
|
|
8
|
-
const fullPath = join(projectRoot, candidate);
|
|
9
|
-
if (existsSync(fullPath)) {
|
|
10
|
-
return fullPath;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return void 0;
|
|
14
|
-
};
|
|
15
|
-
const readWranglerJsonc = (wranglerPath) => {
|
|
16
|
-
const text = readFileSync(wranglerPath, "utf8");
|
|
17
|
-
const parseErrors = [];
|
|
18
|
-
const value = parse(text, parseErrors, { allowTrailingComma: true });
|
|
19
|
-
if (parseErrors.length > 0 || value === null || typeof value !== "object") {
|
|
20
|
-
return { parsed: void 0, text };
|
|
21
|
-
}
|
|
22
|
-
return { parsed: value, text };
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export { WRANGLER_FILES, findWranglerFile, readWranglerJsonc };
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { Project, SyntaxKind } from 'ts-morph';
|
|
2
|
-
import { collectCalls } from './parseSchema-DSeyktvG.mjs';
|
|
3
|
-
|
|
4
|
-
const IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
|
|
5
|
-
const VALIDATOR_METHODS = /* @__PURE__ */ new Set([
|
|
6
|
-
"any",
|
|
7
|
-
"array",
|
|
8
|
-
"bigint",
|
|
9
|
-
"boolean",
|
|
10
|
-
"bytes",
|
|
11
|
-
"date",
|
|
12
|
-
"id",
|
|
13
|
-
"literal",
|
|
14
|
-
"null",
|
|
15
|
-
"number",
|
|
16
|
-
"object",
|
|
17
|
-
"optional",
|
|
18
|
-
"record",
|
|
19
|
-
"storage",
|
|
20
|
-
"string",
|
|
21
|
-
"timestamp",
|
|
22
|
-
"union"
|
|
23
|
-
]);
|
|
24
|
-
const isValidatorExpression = (node) => {
|
|
25
|
-
if (node.getKind() === SyntaxKind.ParenthesizedExpression) {
|
|
26
|
-
return isValidatorExpression(node.asKindOrThrow(SyntaxKind.ParenthesizedExpression).getExpression());
|
|
27
|
-
}
|
|
28
|
-
if (node.getKind() !== SyntaxKind.CallExpression) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
const call = node.asKindOrThrow(SyntaxKind.CallExpression);
|
|
32
|
-
const callee = call.getExpression();
|
|
33
|
-
if (callee.getKind() !== SyntaxKind.PropertyAccessExpression) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
const access = callee.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
|
|
37
|
-
if (access.getExpression().getKind() !== SyntaxKind.Identifier || access.getExpression().getText() !== "v" || !VALIDATOR_METHODS.has(access.getName())) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
return call.getArguments().every((argument) => isValidatorArgument(argument));
|
|
41
|
-
};
|
|
42
|
-
const isValidatorArgument = (node) => {
|
|
43
|
-
const kind = node.getKind();
|
|
44
|
-
if (kind === SyntaxKind.ParenthesizedExpression) {
|
|
45
|
-
return isValidatorArgument(node.asKindOrThrow(SyntaxKind.ParenthesizedExpression).getExpression());
|
|
46
|
-
}
|
|
47
|
-
if (kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.FalseKeyword || kind === SyntaxKind.NullKeyword) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if (kind === SyntaxKind.PrefixUnaryExpression) {
|
|
51
|
-
const unary = node.asKindOrThrow(SyntaxKind.PrefixUnaryExpression);
|
|
52
|
-
return unary.getOperand().getKind() === SyntaxKind.NumericLiteral;
|
|
53
|
-
}
|
|
54
|
-
if (kind === SyntaxKind.ObjectLiteralExpression) {
|
|
55
|
-
return node.asKindOrThrow(SyntaxKind.ObjectLiteralExpression).getProperties().every((property) => {
|
|
56
|
-
if (property.getKind() !== SyntaxKind.PropertyAssignment) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
const initializer = property.getInitializer();
|
|
60
|
-
return initializer !== void 0 && isValidatorArgument(initializer);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
if (kind === SyntaxKind.ArrayLiteralExpression) {
|
|
64
|
-
return node.asKindOrThrow(SyntaxKind.ArrayLiteralExpression).getElements().every((element) => isValidatorArgument(element));
|
|
65
|
-
}
|
|
66
|
-
return isValidatorExpression(node);
|
|
67
|
-
};
|
|
68
|
-
const isAllowedValidatorText = (validator) => {
|
|
69
|
-
if (typeof validator !== "string" || validator.trim().length === 0) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const project = new Project({ compilerOptions: { allowJs: true }, useInMemoryFileSystem: true });
|
|
73
|
-
const sourceFile = project.createSourceFile("validator.ts", `const __v = (${validator});`, { overwrite: true });
|
|
74
|
-
if (project.getProgram().getSyntacticDiagnostics(sourceFile).length > 0) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
if (sourceFile.getStatements().length !== 1) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
const declaration = sourceFile.getVariableDeclaration("__v");
|
|
81
|
-
const initializer = declaration?.getInitializer();
|
|
82
|
-
return initializer !== void 0 && isValidatorExpression(initializer);
|
|
83
|
-
};
|
|
84
|
-
const ADDITIVE_KINDS = /* @__PURE__ */ new Set(["addIndex", "addOptionalColumn", "addTable"]);
|
|
85
|
-
const classifyEdit = (edit) => ADDITIVE_KINDS.has(edit.kind) ? "additive" : "destructive";
|
|
86
|
-
const isIdentifier = (value) => typeof value === "string" && IDENTIFIER_PATTERN.test(value);
|
|
87
|
-
const validateAdditiveEdit = (edit) => {
|
|
88
|
-
if (!isIdentifier(edit.table)) {
|
|
89
|
-
return "invalid-identifier";
|
|
90
|
-
}
|
|
91
|
-
if (edit.kind === "addOptionalColumn") {
|
|
92
|
-
if (!isIdentifier(edit.column)) {
|
|
93
|
-
return "invalid-identifier";
|
|
94
|
-
}
|
|
95
|
-
if (!isAllowedValidatorText(edit.validator)) {
|
|
96
|
-
return "invalid-validator";
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (edit.kind === "addIndex") {
|
|
100
|
-
if (!isIdentifier(edit.name)) {
|
|
101
|
-
return "invalid-identifier";
|
|
102
|
-
}
|
|
103
|
-
if (!Array.isArray(edit.fields) || !edit.fields.every((field) => isIdentifier(field))) {
|
|
104
|
-
return "invalid-identifier";
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return void 0;
|
|
108
|
-
};
|
|
109
|
-
const findTablesObject = (source) => {
|
|
110
|
-
const project = new Project({ compilerOptions: { allowJs: true }, useInMemoryFileSystem: true });
|
|
111
|
-
const sourceFile = project.createSourceFile("schema.ts", source, { overwrite: true });
|
|
112
|
-
let defineSchemaCall;
|
|
113
|
-
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
114
|
-
if (call.getExpression().getText() === "defineSchema") {
|
|
115
|
-
defineSchemaCall = call;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (defineSchemaCall === void 0) {
|
|
120
|
-
const aliased = sourceFile.getImportDeclarations().some((declaration) => declaration.getNamedImports().some((named) => named.getName() === "defineSchema" && named.getAliasNode() !== void 0));
|
|
121
|
-
return { reason: aliased ? "aliased-define-schema" : "no-define-schema" };
|
|
122
|
-
}
|
|
123
|
-
const tablesArgument = defineSchemaCall.getArguments()[0];
|
|
124
|
-
if (tablesArgument?.getKind() !== SyntaxKind.ObjectLiteralExpression) {
|
|
125
|
-
return { reason: "non-object-argument" };
|
|
126
|
-
}
|
|
127
|
-
return { object: tablesArgument.asKindOrThrow(SyntaxKind.ObjectLiteralExpression), sourceFile };
|
|
128
|
-
};
|
|
129
|
-
const findTableProperty = (tablesObject, table) => {
|
|
130
|
-
for (const property of tablesObject.getProperties()) {
|
|
131
|
-
if (property.getKind() === SyntaxKind.PropertyAssignment && property.getNameNode().getText().replaceAll(/["']/gu, "") === table) {
|
|
132
|
-
return property;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return void 0;
|
|
136
|
-
};
|
|
137
|
-
const findDefineTableShape = (tableProperty) => {
|
|
138
|
-
const defineTableCall = tableProperty.getInitializer()?.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => call.getExpression().getText() === "defineTable");
|
|
139
|
-
const shape = defineTableCall?.getArguments()[0];
|
|
140
|
-
return shape?.getKind() === SyntaxKind.ObjectLiteralExpression ? shape.asKindOrThrow(SyntaxKind.ObjectLiteralExpression) : void 0;
|
|
141
|
-
};
|
|
142
|
-
const applyAddTable = (tablesObject, edit) => {
|
|
143
|
-
if (findTableProperty(tablesObject, edit.table) !== void 0) {
|
|
144
|
-
return "duplicate-table";
|
|
145
|
-
}
|
|
146
|
-
tablesObject.addPropertyAssignment({
|
|
147
|
-
initializer: `defineTable({
|
|
148
|
-
// Add your column validators here.
|
|
149
|
-
// Example:
|
|
150
|
-
// text: v.string(),
|
|
151
|
-
// createdAt: v.number(),
|
|
152
|
-
})`,
|
|
153
|
-
name: edit.table
|
|
154
|
-
});
|
|
155
|
-
return void 0;
|
|
156
|
-
};
|
|
157
|
-
const applyAddOptionalColumn = (tablesObject, edit) => {
|
|
158
|
-
const tableProperty = findTableProperty(tablesObject, edit.table);
|
|
159
|
-
if (tableProperty === void 0) {
|
|
160
|
-
return "unknown-table";
|
|
161
|
-
}
|
|
162
|
-
const shape = findDefineTableShape(tableProperty);
|
|
163
|
-
if (shape === void 0) {
|
|
164
|
-
return "unknown-table";
|
|
165
|
-
}
|
|
166
|
-
const exists = shape.getProperties().some(
|
|
167
|
-
(property) => property.getKind() === SyntaxKind.PropertyAssignment && property.getNameNode().getText().replaceAll(/["']/gu, "") === edit.column
|
|
168
|
-
);
|
|
169
|
-
if (exists) {
|
|
170
|
-
return "duplicate-column";
|
|
171
|
-
}
|
|
172
|
-
shape.addPropertyAssignment({ initializer: `v.optional(${edit.validator})`, name: edit.column });
|
|
173
|
-
return void 0;
|
|
174
|
-
};
|
|
175
|
-
const applyAddIndex = (tableProperty, edit) => {
|
|
176
|
-
if (tableProperty === void 0) {
|
|
177
|
-
return "unknown-table";
|
|
178
|
-
}
|
|
179
|
-
const initializer = tableProperty.getInitializer();
|
|
180
|
-
if (initializer === void 0) {
|
|
181
|
-
return "unknown-table";
|
|
182
|
-
}
|
|
183
|
-
const duplicate = collectCalls(initializer).some((call) => {
|
|
184
|
-
const expression = call.getExpression();
|
|
185
|
-
if (expression.getKind() !== SyntaxKind.PropertyAccessExpression) {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
const access = expression.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
|
|
189
|
-
const [nameArgument] = call.getArguments();
|
|
190
|
-
return access.getName() === "index" && nameArgument?.getKind() === SyntaxKind.StringLiteral && nameArgument.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralText() === edit.name;
|
|
191
|
-
});
|
|
192
|
-
if (duplicate) {
|
|
193
|
-
return "duplicate-index";
|
|
194
|
-
}
|
|
195
|
-
const fields = edit.fields.map((field) => JSON.stringify(field)).join(", ");
|
|
196
|
-
const options = edit.unique === true ? ", { unique: true }" : "";
|
|
197
|
-
tableProperty.setInitializer(`${initializer.getText()}.index(${JSON.stringify(edit.name)}, [${fields}]${options})`);
|
|
198
|
-
return void 0;
|
|
199
|
-
};
|
|
200
|
-
const applyAdditiveEdit = (source, edit) => {
|
|
201
|
-
if (classifyEdit(edit) === "destructive") {
|
|
202
|
-
return { ok: false, reason: "destructive" };
|
|
203
|
-
}
|
|
204
|
-
const additive = edit;
|
|
205
|
-
const invalid = validateAdditiveEdit(additive);
|
|
206
|
-
if (invalid !== void 0) {
|
|
207
|
-
return { ok: false, reason: invalid };
|
|
208
|
-
}
|
|
209
|
-
const located = findTablesObject(source);
|
|
210
|
-
if ("reason" in located) {
|
|
211
|
-
return { ok: false, reason: located.reason };
|
|
212
|
-
}
|
|
213
|
-
const { object: tablesObject, sourceFile } = located;
|
|
214
|
-
let failure;
|
|
215
|
-
if (additive.kind === "addTable") {
|
|
216
|
-
failure = applyAddTable(tablesObject, additive);
|
|
217
|
-
} else if (additive.kind === "addOptionalColumn") {
|
|
218
|
-
failure = applyAddOptionalColumn(tablesObject, additive);
|
|
219
|
-
} else {
|
|
220
|
-
failure = applyAddIndex(findTableProperty(tablesObject, additive.table), additive);
|
|
221
|
-
}
|
|
222
|
-
if (failure !== void 0) {
|
|
223
|
-
return { ok: false, reason: failure };
|
|
224
|
-
}
|
|
225
|
-
return { ok: true, text: sourceFile.getFullText() };
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
export { applyAdditiveEdit, classifyEdit };
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { normalize, join, relative, isAbsolute, dirname } from 'node:path';
|
|
4
|
-
|
|
5
|
-
const loadStudioAssets = (logger, resolveFrom = import.meta.url) => {
|
|
6
|
-
try {
|
|
7
|
-
const require = createRequire(resolveFrom);
|
|
8
|
-
return {
|
|
9
|
-
script: readFileSync(require.resolve("@lunora/studio/standalone/studio.js")),
|
|
10
|
-
styles: readFileSync(require.resolve("@lunora/studio/styles.css"))
|
|
11
|
-
};
|
|
12
|
-
} catch (error) {
|
|
13
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
14
|
-
logger?.warnOnce?.(`[lunora] studio assets unavailable (build @lunora/studio?): ${message}`);
|
|
15
|
-
return void 0;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const studioAssetsStamp = (resolveFrom = import.meta.url) => {
|
|
19
|
-
try {
|
|
20
|
-
const require = createRequire(resolveFrom);
|
|
21
|
-
const scriptMtime = statSync(require.resolve("@lunora/studio/standalone/studio.js")).mtimeMs;
|
|
22
|
-
const stylesMtime = statSync(require.resolve("@lunora/studio/styles.css")).mtimeMs;
|
|
23
|
-
return Math.max(scriptMtime, stylesMtime);
|
|
24
|
-
} catch {
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
const resolveStandaloneDirectory = (resolveFrom = import.meta.url) => {
|
|
29
|
-
try {
|
|
30
|
-
return dirname(createRequire(resolveFrom).resolve("@lunora/studio/standalone/studio.js"));
|
|
31
|
-
} catch {
|
|
32
|
-
return void 0;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const resolveContainedFile = (directory, fileName) => {
|
|
36
|
-
if (fileName === "" || fileName === "." || fileName === ".." || fileName.includes("/") || fileName.includes("\\") || fileName.includes("\0")) {
|
|
37
|
-
return void 0;
|
|
38
|
-
}
|
|
39
|
-
const resolved = normalize(join(directory, fileName));
|
|
40
|
-
const relativePath = relative(directory, resolved);
|
|
41
|
-
if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath) || relativePath.includes("/") || relativePath.includes("\\")) {
|
|
42
|
-
return void 0;
|
|
43
|
-
}
|
|
44
|
-
return resolved;
|
|
45
|
-
};
|
|
46
|
-
const readStandaloneAsset = (fileName, resolveFrom = import.meta.url) => {
|
|
47
|
-
const directory = resolveStandaloneDirectory(resolveFrom);
|
|
48
|
-
if (directory === void 0) {
|
|
49
|
-
return void 0;
|
|
50
|
-
}
|
|
51
|
-
const resolved = resolveContainedFile(directory, fileName);
|
|
52
|
-
if (resolved === void 0) {
|
|
53
|
-
return void 0;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
return readFileSync(resolved);
|
|
57
|
-
} catch {
|
|
58
|
-
return void 0;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
const isStandaloneModulePath = (pathname) => pathname.endsWith(".js") || pathname.endsWith(".js.map");
|
|
62
|
-
const assetContentType = (fileName) => {
|
|
63
|
-
if (fileName.endsWith(".css")) {
|
|
64
|
-
return "text/css; charset=utf-8";
|
|
65
|
-
}
|
|
66
|
-
if (fileName.endsWith(".map")) {
|
|
67
|
-
return "application/json; charset=utf-8";
|
|
68
|
-
}
|
|
69
|
-
return "text/javascript; charset=utf-8";
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export { assetContentType, isStandaloneModulePath, loadStudioAssets, readStandaloneAsset, resolveContainedFile, resolveStandaloneDirectory, studioAssetsStamp };
|