@svgrid/enterprise 3.1.17 → 3.1.18
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/SvGridEditPanel.svelte +14 -1
- package/dist/SvGridScheduler.svelte +15 -6
- package/dist/SvSheet.svelte +2 -0
- package/dist/cdn/svgrid-enterprise.svelte-external.js +6582 -6501
- package/dist/designer/assets/{index-C1Ujg62Z.js → index-DKudY-XD.js} +357 -346
- package/dist/designer/assets/index-WCxj6WfJ.css +1 -0
- package/dist/designer/index.html +2 -2
- package/dist/fields/SvBase.svelte +36 -33
- package/dist/fields/base-connect.js +50 -9
- package/dist/fields/base-sql.d.ts +8 -0
- package/dist/fields/base-sql.js +12 -3
- package/dist/install.js +3 -3
- package/dist/node/studio.js +205 -60
- package/dist/sheet-enable.d.ts +8 -2
- package/dist/sheet-enable.js +21 -9
- package/dist/sources/introspect-supabase.d.ts +6 -3
- package/dist/sources/introspect-supabase.js +29 -11
- package/dist/sources/supabase.d.ts +10 -0
- package/dist/sources/supabase.js +11 -2
- package/dist/studio/cli.d.ts +9 -0
- package/dist/studio/cli.js +41 -7
- package/dist/studio/emit-project.d.ts +3 -1
- package/dist/studio/emit-project.js +84 -33
- package/dist/studio/emit-schema.js +9 -2
- package/dist/studio/index.d.ts +1 -1
- package/dist/studio/init-flow.d.ts +7 -0
- package/dist/studio/init-flow.js +23 -2
- package/dist/studio/introspect-db.d.ts +15 -1
- package/dist/studio/introspect-db.js +29 -5
- package/dist/sveltekit/sql-source.d.ts +24 -0
- package/dist/sveltekit/sql-source.js +34 -1
- package/dist/sveltekit/sql.js +14 -2
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/wc/GridFooter-BgoA77TJ.js +145 -0
- package/dist/wc/{GridMenus-q9qYpzKh.js → GridMenus-DVGuqp2l.js} +430 -430
- package/dist/wc/{PinSplitter-D-qtDhSM.js → PinSplitter-Btco0txr.js} +24 -24
- package/dist/wc/{SvChartMenu-CQNrQGT2.js → SvChartMenu-Ue-HFR5c.js} +58 -58
- package/dist/wc/{SvDateRangeInput-BYNOfYxD.js → SvDateRangeInput-CJZohgBe.js} +43 -43
- package/dist/wc/{SvDateTimePicker-Bq9N0Oiy.js → SvDateTimePicker-DmK_QkkU.js} +241 -241
- package/dist/wc/{SvField-B_kurx_P.js → SvField-C-OodMMv.js} +71 -71
- package/dist/wc/SvGridCellEditor-C7ios4i_.js +582 -0
- package/dist/wc/{SvGridChart-DrmqEen2.js → SvGridChart-DWRqlhCV.js} +549 -549
- package/dist/wc/{SvGridChartBuilder-XFvW1BdT.js → SvGridChartBuilder-BXW3yUR0.js} +337 -337
- package/dist/wc/{SvGridChartPanel-oJJtUKG6.js → SvGridChartPanel-28n4tomx.js} +354 -354
- package/dist/wc/{SvGridChartView-Cetuj70H.js → SvGridChartView-CbG_WrU-.js} +7 -7
- package/dist/wc/{SvGridDropdown-45WsObLL.js → SvGridDropdown-BVnpNU_I.js} +130 -130
- package/dist/wc/{SvModal-DrfOYYyX.js → SvModal-DLppAQED.js} +91 -91
- package/dist/wc/{client-DQIzjDBG.js → client-GWAV79ON.js} +479 -476
- package/dist/wc/{date-format-DS-z_h9v.js → date-format-4_QoS4zZ.js} +393 -393
- package/dist/wc/{grid-extension.svelte-Bufq4B4C.js → grid-extension.svelte-CucBS4FL.js} +1 -1
- package/dist/wc/sv-sheet-element.js +10474 -10460
- package/dist/wc/{svelte-virtualizer.svelte-CkZaFakp.js → svelte-virtualizer.svelte-BDcouWqw.js} +3 -3
- package/package.json +17 -4
- package/dist/designer/assets/index-DdmxO6Dd.css +0 -1
- package/dist/wc/GridFooter-4T29pfok.js +0 -145
- package/dist/wc/SvGridCellEditor-Nnl8C3Yj.js +0 -582
package/dist/node/studio.js
CHANGED
|
@@ -1151,7 +1151,7 @@ function mapSqlType(raw) {
|
|
|
1151
1151
|
if (/json/.test(t)) return "json";
|
|
1152
1152
|
return "text";
|
|
1153
1153
|
}
|
|
1154
|
-
function fieldFrom(name, rawType, pk, notNull) {
|
|
1154
|
+
function fieldFrom(name, rawType, pk, notNull, generated = false) {
|
|
1155
1155
|
const field = {
|
|
1156
1156
|
field: name,
|
|
1157
1157
|
type: mapSqlType(rawType)
|
|
@@ -1159,12 +1159,26 @@ function fieldFrom(name, rawType, pk, notNull) {
|
|
|
1159
1159
|
if (pk) {
|
|
1160
1160
|
field.primaryKey = true;
|
|
1161
1161
|
field.readonly = true;
|
|
1162
|
-
} else if (
|
|
1162
|
+
} else if (generated) field.readonly = true;
|
|
1163
|
+
else if (notNull) field.required = true;
|
|
1163
1164
|
return field;
|
|
1164
1165
|
}
|
|
1165
|
-
/**
|
|
1166
|
+
/**
|
|
1167
|
+
* A Base table's order column stays in the schema (base-connect finds it there
|
|
1168
|
+
* and reads in its order) but is never shown or written by a generated app.
|
|
1169
|
+
*/
|
|
1170
|
+
function hideBaseOrderColumn(field) {
|
|
1171
|
+
if (field.field !== "svgrid_order") return field;
|
|
1172
|
+
const { required: _required, ...rest } = field;
|
|
1173
|
+
return {
|
|
1174
|
+
...rest,
|
|
1175
|
+
readonly: true,
|
|
1176
|
+
hidden: true
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
/** Map catalog rows (aliased name/type/nullable/pk/generated) from information_schema dialects. */
|
|
1166
1180
|
function infoSchemaFields(rows) {
|
|
1167
|
-
return rows.map((r) => fieldFrom(String(r.name), String(r.type ?? ""), truthy(r.pk), !truthy(r.nullable)));
|
|
1181
|
+
return rows.map((r) => hideBaseOrderColumn(fieldFrom(String(r.name), String(r.type ?? ""), truthy(r.pk), !truthy(r.nullable), truthy(r.generated))));
|
|
1168
1182
|
}
|
|
1169
1183
|
/** Map SQLite `PRAGMA table_info` rows (name/type/notnull/pk-as-index). */
|
|
1170
1184
|
function sqliteFields(rows) {
|
|
@@ -1191,7 +1205,8 @@ var DIALECTS$1 = {
|
|
|
1191
1205
|
columns: (table) => ({
|
|
1192
1206
|
sql: `SELECT c.column_name AS name, c.data_type AS type,
|
|
1193
1207
|
CASE WHEN c.is_nullable = 'YES' THEN 1 ELSE 0 END AS nullable,
|
|
1194
|
-
CASE WHEN pk.column_name IS NOT NULL THEN 1 ELSE 0 END AS pk
|
|
1208
|
+
CASE WHEN pk.column_name IS NOT NULL THEN 1 ELSE 0 END AS pk,
|
|
1209
|
+
CASE WHEN (c.is_identity = 'YES' AND c.identity_generation = 'ALWAYS') OR c.is_generated = 'ALWAYS' THEN 1 ELSE 0 END AS generated
|
|
1195
1210
|
FROM information_schema.columns c
|
|
1196
1211
|
LEFT JOIN (
|
|
1197
1212
|
SELECT kcu.column_name
|
|
@@ -1226,7 +1241,8 @@ WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1`,
|
|
|
1226
1241
|
columns: (table) => ({
|
|
1227
1242
|
sql: `SELECT COLUMN_NAME AS name, DATA_TYPE AS type,
|
|
1228
1243
|
CASE WHEN IS_NULLABLE = 'YES' THEN 1 ELSE 0 END AS nullable,
|
|
1229
|
-
CASE WHEN COLUMN_KEY = 'PRI' THEN 1 ELSE 0 END AS pk
|
|
1244
|
+
CASE WHEN COLUMN_KEY = 'PRI' THEN 1 ELSE 0 END AS pk,
|
|
1245
|
+
CASE WHEN EXTRA LIKE '%GENERATED%' THEN 1 ELSE 0 END AS generated
|
|
1230
1246
|
FROM information_schema.columns
|
|
1231
1247
|
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE()
|
|
1232
1248
|
ORDER BY ORDINAL_POSITION`,
|
|
@@ -16215,7 +16231,11 @@ function layoutFile(nav, opts = {}) {
|
|
|
16215
16231
|
contents: `<script lang="ts">
|
|
16216
16232
|
import '../app.css'
|
|
16217
16233
|
import '../custom.css'
|
|
16218
|
-
import { page } from '$app/state'
|
|
16234
|
+
import { page } from '$app/state'
|
|
16235
|
+
import { setLicenseKey } from '@svgrid/enterprise'${opts.access ? `\n import { currentRole, canScreen } from '$lib/access'` : ""}${opts.i18n ? `\n import { t, currentLocale, locales } from '$lib/i18n'` : ""}${opts.auth ? `\n import type { LayoutData } from './$types'` : ""}${opts.supabaseAuth ? `\n import { SvAuthGate, type SupabaseAuthClientLike } from '@svgrid/enterprise'\n import { supabaseClient } from '$lib/connections'` : ""}
|
|
16236
|
+
|
|
16237
|
+
// VITE_SVPRO_KEY in .env is your SvGrid license key; it removes the unlicensed watermark.
|
|
16238
|
+
if (import.meta.env.VITE_SVPRO_KEY) setLicenseKey(import.meta.env.VITE_SVPRO_KEY)
|
|
16219
16239
|
|
|
16220
16240
|
let { children${opts.auth ? ", data" : ""} }${opts.auth ? ": { children: import('svelte').Snippet; data: LayoutData }" : ""} = $props()
|
|
16221
16241
|
const nav = ${JSON.stringify(links)}
|
|
@@ -16430,11 +16450,18 @@ function emitStudioApp(schemas) {
|
|
|
16430
16450
|
label: n.label
|
|
16431
16451
|
};
|
|
16432
16452
|
});
|
|
16453
|
+
const customCss = {
|
|
16454
|
+
path: "src/custom.css",
|
|
16455
|
+
description: "Your custom styles (imported after app.css so it overrides).",
|
|
16456
|
+
userOwned: true,
|
|
16457
|
+
contents: "/* Your custom styles. Imported after app.css, so these rules override the defaults. */\n"
|
|
16458
|
+
};
|
|
16433
16459
|
return [
|
|
16434
16460
|
...files,
|
|
16435
16461
|
...prepared.map((s) => entityScreenPage(s)),
|
|
16436
16462
|
layoutFile(nav),
|
|
16437
|
-
homeFile(nav)
|
|
16463
|
+
homeFile(nav),
|
|
16464
|
+
customCss
|
|
16438
16465
|
];
|
|
16439
16466
|
}
|
|
16440
16467
|
//#endregion
|
|
@@ -16457,7 +16484,7 @@ function hasFieldConditions(schema) {
|
|
|
16457
16484
|
*
|
|
16458
16485
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
16459
16486
|
*/
|
|
16460
|
-
var SVGRID_VERSION = "3.1.
|
|
16487
|
+
var SVGRID_VERSION = "3.1.18";
|
|
16461
16488
|
/**
|
|
16462
16489
|
* The version of `@svgrid/grid`, which releases on its own line: enterprise is
|
|
16463
16490
|
* on 3.1.x while the grid is on 3.0.x.
|
|
@@ -16470,7 +16497,7 @@ var SVGRID_VERSION = "3.1.17";
|
|
|
16470
16497
|
*
|
|
16471
16498
|
* `version.test.ts` asserts this matches packages/grid/package.json.
|
|
16472
16499
|
*/
|
|
16473
|
-
var GRID_VERSION = "3.0.
|
|
16500
|
+
var GRID_VERSION = "3.0.19";
|
|
16474
16501
|
//#endregion
|
|
16475
16502
|
//#region src/studio/emit-project.ts
|
|
16476
16503
|
var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
|
|
@@ -16501,15 +16528,24 @@ function columnFormatExpr(fmt) {
|
|
|
16501
16528
|
}
|
|
16502
16529
|
}
|
|
16503
16530
|
/** The visible grid columns: configured order, per-column header/width/align overrides, editability per mode. */
|
|
16504
|
-
function gridColumnsExpr(schemaVar, block, typeName) {
|
|
16531
|
+
function gridColumnsExpr(schemaVar, block, typeName, relDisplay) {
|
|
16505
16532
|
if (block.config.kind !== "grid") return `schemaToColumns(${schemaVar})`;
|
|
16506
16533
|
const cfg = block.config;
|
|
16507
16534
|
const idSafe = block.id.replace(/-/g, "_");
|
|
16508
|
-
const
|
|
16535
|
+
const shown = cfg.columns.filter((c) => c.show);
|
|
16536
|
+
const shownFields = new Set(shown.map((c) => c.field));
|
|
16537
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16538
|
+
const visible = shown.map((c) => {
|
|
16539
|
+
const d = relDisplay?.get(c.field);
|
|
16540
|
+
return {
|
|
16541
|
+
c,
|
|
16542
|
+
col: d && !shownFields.has(d) ? d : c.field
|
|
16543
|
+
};
|
|
16544
|
+
}).filter((v) => !seen.has(v.col) && !!seen.add(v.col));
|
|
16509
16545
|
const editablePart = cfg.editing === "inline" ? "" : ", editable: false";
|
|
16510
16546
|
if (visible.length === 0) return editablePart ? `schemaToColumns(${schemaVar}).map((c) => ({ ...c${editablePart} }))` : `schemaToColumns(${schemaVar})`;
|
|
16511
|
-
const order = visible.map((
|
|
16512
|
-
const ovEntries = visible.map((c) => {
|
|
16547
|
+
const order = visible.map((v) => `'${v.col}'`);
|
|
16548
|
+
const ovEntries = visible.map(({ c, col }) => {
|
|
16513
16549
|
const parts = [];
|
|
16514
16550
|
if (c.header) parts.push(`header: ${JSON.stringify(c.header)}`);
|
|
16515
16551
|
if (c.width != null) parts.push(`width: ${c.width}`);
|
|
@@ -16518,7 +16554,7 @@ function gridColumnsExpr(schemaVar, block, typeName) {
|
|
|
16518
16554
|
if (cfg.treeData && c.field === cfg.treeData.labelField && typeName) parts.push(`cell: (ctx: CellContext<${typeName}>) => renderSnippet(treeCell_${idSafe}, { value: ctx.getValue(), row: ctx.row.original })`);
|
|
16519
16555
|
else if (c.cellType && typeName) parts.push(`cell: (ctx: CellContext<${typeName}>) => renderSnippet(${cellSnippetName(idSafe, c.field)}, { value: ctx.getValue() })`);
|
|
16520
16556
|
else if (c.format) parts.push(`format: ${columnFormatExpr(c.format)}`);
|
|
16521
|
-
return parts.length ? `'${
|
|
16557
|
+
return parts.length ? `'${col}': { ${parts.join(", ")} }` : null;
|
|
16522
16558
|
}).filter((x) => !!x);
|
|
16523
16559
|
const ovDecl = ovEntries.length ? ` const ov: Record<string, Partial<(typeof all)[number]>> = { ${ovEntries.join(", ")} };` : "";
|
|
16524
16560
|
const ovPart = ovEntries.length ? ", ...(ov[String(c.field)] ?? {})" : "";
|
|
@@ -18240,7 +18276,7 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
|
|
|
18240
18276
|
if (blocks.some((b) => b.config.kind === "grid" && !!b.config.export?.print)) parts.push(PRINT_HELPER);
|
|
18241
18277
|
for (const b of blocks) if (b.config.kind === "grid") {
|
|
18242
18278
|
const idSafe = b.id.replace(/-/g, "_");
|
|
18243
|
-
const colExpr = gridColumnsExpr(n.schemaVar, b, n.type);
|
|
18279
|
+
const colExpr = gridColumnsExpr(n.schemaVar, b, n.type, relationDisplayFields(rawSchema, resolve));
|
|
18244
18280
|
let colValue = i18nEnabled ? `localizeCols(${colExpr}, ${JSON.stringify(schema.name)}, $t)` : colExpr;
|
|
18245
18281
|
const actions = (b.config.rowActions ?? []).filter((a) => a.kind !== "edit" || wantsForm);
|
|
18246
18282
|
if (actions.length) {
|
|
@@ -18550,7 +18586,7 @@ function attrs(f) {
|
|
|
18550
18586
|
return out.length ? " " + out.join(" ") : "";
|
|
18551
18587
|
}
|
|
18552
18588
|
/** The `+page.server.ts` + SSR `+page.svelte` for a single-grid CRUD screen. */
|
|
18553
|
-
function emitSsrGridScreen(schema, screen, routed, accessEnabled, screenIds, byName) {
|
|
18589
|
+
function emitSsrGridScreen(schema, screen, routed, accessEnabled, screenIds, byName, rawSchema = schema) {
|
|
18554
18590
|
const n = namesFor(schema);
|
|
18555
18591
|
const isSql = routed(schema.name);
|
|
18556
18592
|
const relSchemaOf = (f) => byName.get(f.relation.entity);
|
|
@@ -18567,18 +18603,23 @@ function emitSsrGridScreen(schema, screen, routed, accessEnabled, screenIds, byN
|
|
|
18567
18603
|
const normType = (t) => t === "number" ? "number" : t === "boolean" ? "boolean" : "text";
|
|
18568
18604
|
const fieldTypesLit = `{ ${formFields.map((f) => `${jsStr(f.field)}: ${jsStr(normType(f.type))}`).join(", ")} }`;
|
|
18569
18605
|
const isRel = (f) => f.type === "relation" && !!f.relation && byName.has(f.relation.entity);
|
|
18606
|
+
const relSourceOf = (rs) => routed(rs.name) ? `createKitDataSource<Record<string, unknown>>({ endpoint: ${jsStr("/api/" + namesFor(rs).route)}, fetch })` : namesFor(rs).sourceVar;
|
|
18607
|
+
const relDisplay = relationDisplayFields(rawSchema, (name) => byName.get(name));
|
|
18608
|
+
const labelFields = isSql ? schema.fields.filter((f) => isRel(f) && schema.fields.some((x) => x.field === relDisplay.get(f.field))) : [];
|
|
18570
18609
|
const relFields = formFields.filter(isRel);
|
|
18571
18610
|
const relRouted = relFields.some((f) => routed(relSchemaOf(f).name));
|
|
18572
|
-
const relSourceVars = [...new Set(relFields.filter((f) => !routed(relSchemaOf(f).name)).map((f) => namesFor(relSchemaOf(f)).sourceVar))];
|
|
18611
|
+
const relSourceVars = [...new Set([...relFields, ...labelFields].filter((f) => !routed(relSchemaOf(f).name)).map((f) => namesFor(relSchemaOf(f)).sourceVar))];
|
|
18573
18612
|
const relPrefetch = relFields.map((f) => {
|
|
18574
18613
|
const rs = relSchemaOf(f);
|
|
18575
|
-
const rel = namesFor(rs);
|
|
18576
18614
|
const relId = relIdOf(rs);
|
|
18577
18615
|
const labelF = f.relation.labelField ?? relId;
|
|
18578
|
-
const
|
|
18579
|
-
return ` const ${f.field}Options = (await ${relSource}.getRows({ startRow: 0, endRow: 100, pageIndex: 0, pageSize: 100, sortModel: [], filterModel: {} })).rows.map((r: Record<string, unknown>) => ({ value: String(r[${jsStr(relId)}] ?? ''), label: String(r[${jsStr(labelF)}] ?? '') }))`;
|
|
18616
|
+
return ` const ${f.field}Options = (await ${relSourceOf(rs)}.getRows({ startRow: 0, endRow: 100, pageIndex: 0, pageSize: 100, sortModel: [], filterModel: {} })).rows.map((r: Record<string, unknown>) => ({ value: String(r[${jsStr(relId)}] ?? ''), label: String(r[${jsStr(labelF)}] ?? '') }))`;
|
|
18580
18617
|
}).join("\n");
|
|
18581
18618
|
const relReturn = relFields.map((f) => `, ${f.field}Options`).join("");
|
|
18619
|
+
const labelSpecs = labelFields.map((f) => {
|
|
18620
|
+
const rs = relSchemaOf(f);
|
|
18621
|
+
return `{ fk: ${jsStr(f.field)}, as: ${jsStr(relDisplay.get(f.field))}, lookup: createRelationLookup({ source: ${relSourceOf(rs)}, valueField: ${jsStr(relIdOf(rs))}, labelField: ${jsStr(f.relation.labelField ?? relIdOf(rs))} }) }`;
|
|
18622
|
+
});
|
|
18582
18623
|
const src = isSql ? "source(fetch)" : n.sourceVar;
|
|
18583
18624
|
const fetchArg = isSql ? ", fetch" : "";
|
|
18584
18625
|
const loadFetchArg = isSql || relRouted ? ", fetch" : "";
|
|
@@ -18587,7 +18628,8 @@ function emitSsrGridScreen(schema, screen, routed, accessEnabled, screenIds, byN
|
|
|
18587
18628
|
const enterpriseImports = [
|
|
18588
18629
|
"validateAll",
|
|
18589
18630
|
...conditional ? ["stripHiddenValues"] : [],
|
|
18590
|
-
...isSql || relRouted ? ["createKitDataSource"] : []
|
|
18631
|
+
...isSql || relRouted ? ["createKitDataSource"] : [],
|
|
18632
|
+
...labelSpecs.length ? ["createRelationLookup", "withRelationLabels"] : []
|
|
18591
18633
|
].join(", ");
|
|
18592
18634
|
const dataImports = [.../* @__PURE__ */ new Set([...isSql ? [] : [n.sourceVar], ...relSourceVars])];
|
|
18593
18635
|
const sourceImport = dataImports.length ? `import { ${dataImports.join(", ")} } from '$lib/data'\n` : "";
|
|
@@ -18625,7 +18667,7 @@ function formToValues(fd: FormData): Record<string, unknown> {
|
|
|
18625
18667
|
|
|
18626
18668
|
export const load: PageServerLoad = async ({ url${loadFetchArg}${localsArg} }) => {
|
|
18627
18669
|
${readGuard} const plan = planFromSearchParams(url, ${pageSize})
|
|
18628
|
-
const { rows, rowCount } = await ${src}.getRows(plan)
|
|
18670
|
+
const { rows, rowCount } = await ${labelSpecs.length ? `withRelationLabels(${src}, [\n ${labelSpecs.join(",\n ")},\n ])` : src}.getRows(plan)
|
|
18629
18671
|
${relPrefetch ? relPrefetch + "\n" : ""} return { rows, total: rowCount, page: plan.pageIndex, size: plan.pageSize, sort: plan.sortModel${relReturn} }
|
|
18630
18672
|
}
|
|
18631
18673
|
|
|
@@ -18981,7 +19023,7 @@ function emitStudioProject(project) {
|
|
|
18981
19023
|
const schema = byName.get(screen.entity);
|
|
18982
19024
|
if (!schema) throw new Error(`emitStudioProject: screen "${screen.title}" references missing entity "${screen.entity}"`);
|
|
18983
19025
|
if (isSsrScreen(project, screen)) {
|
|
18984
|
-
if (ssrScreenShape(project, screen) === "grid") pages.push(...emitSsrGridScreen(schema, screen, routed, accessEnabled, screensByEntity.get(screen.entity) ?? [], byName));
|
|
19026
|
+
if (ssrScreenShape(project, screen) === "grid") pages.push(...emitSsrGridScreen(schema, screen, routed, accessEnabled, screensByEntity.get(screen.entity) ?? [], byName, rawByName.get(screen.entity) ?? schema));
|
|
18985
19027
|
else pages.push(ssrReadServerFile(schema, screen, routed, accessEnabled, screensByEntity.get(screen.entity) ?? [], resolve), screenPage(schema, rawByName.get(screen.entity) ?? schema, screen, resolve, (name) => rawByName.get(name), accessEnabled, i18nEnabled, routeById, drillEnabled, true));
|
|
18986
19028
|
continue;
|
|
18987
19029
|
}
|
|
@@ -21106,7 +21148,7 @@ jobs:
|
|
|
21106
21148
|
- uses: actions/checkout@v4
|
|
21107
21149
|
- uses: actions/setup-node@v4
|
|
21108
21150
|
with:
|
|
21109
|
-
node-version:
|
|
21151
|
+
node-version: 22
|
|
21110
21152
|
- run: npm install
|
|
21111
21153
|
- run: npm run build
|
|
21112
21154
|
- run: npm test
|
|
@@ -21125,7 +21167,7 @@ jobs:
|
|
|
21125
21167
|
- uses: actions/checkout@v4
|
|
21126
21168
|
- uses: actions/setup-node@v4
|
|
21127
21169
|
with:
|
|
21128
|
-
node-version:
|
|
21170
|
+
node-version: 22
|
|
21129
21171
|
- run: npm install
|
|
21130
21172
|
${steps}`;
|
|
21131
21173
|
}
|
|
@@ -21207,7 +21249,7 @@ function deployPlan(project) {
|
|
|
21207
21249
|
files: [{
|
|
21208
21250
|
path: "Dockerfile",
|
|
21209
21251
|
description: "Container image (multi-stage: build then run `node build`).",
|
|
21210
|
-
contents: `# Build stage\nFROM node:
|
|
21252
|
+
contents: `# Build stage\nFROM node:22-alpine AS build\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\nCOPY . .\nRUN npm run build\nRUN npm prune --omit=dev\n\n# Run stage\nFROM node:22-alpine\nWORKDIR /app\nENV NODE_ENV=production\nCOPY --from=build /app/build ./build\nCOPY --from=build /app/node_modules ./node_modules\nCOPY --from=build /app/package.json ./package.json\nEXPOSE 3000\nCMD ["node", "build"]\n`
|
|
21211
21253
|
}, {
|
|
21212
21254
|
path: ".dockerignore",
|
|
21213
21255
|
description: "Files kept out of the image.",
|
|
@@ -21243,13 +21285,21 @@ function studioDeployInfo(project) {
|
|
|
21243
21285
|
}
|
|
21244
21286
|
/** DEPLOY.md - the runbook: required env vars, then Git-integration / CI / CLI paths. */
|
|
21245
21287
|
function deployDocs(project, plan, envKeys, hasMigrations, opts = {}) {
|
|
21246
|
-
const envBlock = envKeys.length ? `## Environment variables\n\nSet these on your host (and locally in \`.env\` - copy \`.env.example\`):\n\n${envKeys.map((k) => `- \`${k}\``).join("\n")}\n\nOptional: \`VITE_SVPRO_KEY\` (your SvGrid license key) removes the unlicensed watermark -
|
|
21288
|
+
const envBlock = envKeys.length ? `## Environment variables\n\nSet these on your host (and locally in \`.env\` - copy \`.env.example\`):\n\n${envKeys.map((k) => `- \`${k}\``).join("\n")}\n\nOptional: \`VITE_SVPRO_KEY\` (your SvGrid license key) removes the unlicensed watermark - the root layout passes it to \`setLicenseKey\`. It is read at build time, so set it before \`npm run build\`.\n\n` : "";
|
|
21247
21289
|
const migrations = hasMigrations ? `## Database migrations\n\nThis app has a typed Drizzle schema (\`src/lib/server/db/schema.ts\`). Create + apply the tables:\n\n\`\`\`bash\nnpm run db:generate # SQL migrations from the schema\nnpm run db:migrate # apply them to DATABASE_URL\n${opts.hasSeedScript ? "npm run db:seed # optional: insert the sample rows\n" : ""}\`\`\`\n\n(Run these against your production database as part of your release step.)\n\n` : "";
|
|
21248
21290
|
const ddl = opts.hasDdl ? `## Database tables\n\nThis app's SQL entities expect their tables to exist. Run \`db/schema.sql\` against the database your \`DATABASE_URL\` points at (psql / mysql / sqlite3 / sqlcmd, or your provider's SQL console) before first use - and optionally \`db/seed.sql\` for sample rows. Tip: enable the Drizzle data layer in Studio for real, versioned migrations instead.\n\n` : "";
|
|
21249
|
-
const
|
|
21250
|
-
|
|
21291
|
+
const letters = [
|
|
21292
|
+
"A",
|
|
21293
|
+
"B",
|
|
21294
|
+
"C"
|
|
21295
|
+
];
|
|
21296
|
+
const gitLetter = plan.dashboard ? letters.shift() : "";
|
|
21297
|
+
const ciLetter = plan.deployWorkflow ? letters.shift() : "";
|
|
21298
|
+
const cliLetter = letters.shift();
|
|
21299
|
+
const gitIntegration = plan.dashboard ? `## Option ${gitLetter} - Git integration (simplest, no CI secrets)\n\n1. Push this repo to GitHub.\n2. Import it at <${plan.dashboard}> - the SvelteKit build is auto-detected.\n${envKeys.length ? "3. Add the environment variables above in the host's dashboard.\n" : ""}\nEvery push to \`main\` then redeploys automatically.\n\n` : "";
|
|
21300
|
+
const ci = plan.deployWorkflow ? `## Option ${ciLetter} - GitHub Actions (included)\n\n\`.github/workflows/deploy.yml\` deploys on every push to \`main\`. Add these repository secrets to enable it (Settings -> Secrets and variables -> Actions):\n\n${plan.secrets.map((s) => `- \`${s}\``).join("\n")}\n\nUntil the secrets are set the deploy step is skipped, so CI stays green.${gitLetter ? ` Prefer Option ${gitLetter}? Delete \`deploy.yml\`.` : ""}\n\n` : "";
|
|
21251
21301
|
const dockerNote = project.deploy === "node" ? `## Container\n\nA \`Dockerfile\` is included:\n\n\`\`\`bash\ndocker build -t ${appSlug(project.title)} .\ndocker run -p 3000:3000 --env-file .env ${appSlug(project.title)}\n\`\`\`\n\nThe server listens on \`PORT\` (default 3000).\n\n` : "";
|
|
21252
|
-
const cli = `## Option
|
|
21302
|
+
const cli = `## Option ${cliLetter} - Deploy from your machine\n\n\`\`\`bash\nnpm run deploy\n\`\`\`\n\n(runs \`${plan.deployScript}\`)\n`;
|
|
21253
21303
|
return `# Deploying ${project.title}\n\nConfigured for **${plan.label}** (SvelteKit \`${plan.adapterModule}\`). A CI workflow (\`.github/workflows/ci.yml\`) builds + tests every push.\n\n${envBlock}${ddl}${migrations}${gitIntegration}${ci}${dockerNote}${cli}`;
|
|
21254
21304
|
}
|
|
21255
21305
|
/** The env-var keys the generated app reads (for DEPLOY.md). */
|
|
@@ -21288,13 +21338,20 @@ function supabaseConnOf(project) {
|
|
|
21288
21338
|
* random `SESSION_SECRET` filled in, so the app is secure out of the box (no
|
|
21289
21339
|
* hardcoded dev secret), and the Supabase URL + anon key the model already
|
|
21290
21340
|
* holds, so a Supabase app runs as generated instead of failing on an empty
|
|
21291
|
-
* URL. Only emitted when there is something to fill in.
|
|
21292
|
-
|
|
21341
|
+
* URL. Only emitted when there is something to fill in.
|
|
21342
|
+
*
|
|
21343
|
+
* `databaseUrl` is the connection the app was built from (`studio init --db
|
|
21344
|
+
* sqlite --url ./shop.db`). Only a SQLite file path is written: it is not a
|
|
21345
|
+
* secret, and without it the app opens an empty data.db. Server URLs carry a
|
|
21346
|
+
* password, so those stay for the user to paste. */
|
|
21347
|
+
function envDotFile(allSource, project, databaseUrl) {
|
|
21293
21348
|
const example = envExample(allSource);
|
|
21294
21349
|
if (!example) return null;
|
|
21295
21350
|
const supa = allSource.includes("PUBLIC_SUPABASE_URL") ? supabaseConnOf(project) : null;
|
|
21296
|
-
|
|
21351
|
+
const sqliteFile = databaseUrl && allSource.includes("import Database from 'better-sqlite3'") && /^DATABASE_URL=$/m.test(example) ? databaseUrl : null;
|
|
21352
|
+
if (!allSource.includes("SESSION_SECRET") && !supa && !sqliteFile) return null;
|
|
21297
21353
|
let env = example.replace("SESSION_SECRET=change-me-to-a-long-random-string", `SESSION_SECRET=${randomSecret()}`);
|
|
21354
|
+
if (sqliteFile) env = env.replace(/^DATABASE_URL=$/m, `DATABASE_URL=${sqliteFile}`);
|
|
21298
21355
|
if (supa) env = env.replace(/^PUBLIC_SUPABASE_URL=$/m, `PUBLIC_SUPABASE_URL=${supa.url}`).replace(/^PUBLIC_SUPABASE_ANON_KEY=$/m, `PUBLIC_SUPABASE_ANON_KEY=${supa.key}`);
|
|
21299
21356
|
return env;
|
|
21300
21357
|
}
|
|
@@ -21376,7 +21433,7 @@ function runtimeDeps(project, allSource) {
|
|
|
21376
21433
|
if (allSource.includes("import pg from 'pg'")) dependencies["pg"] = "^8.11.0";
|
|
21377
21434
|
if (allSource.includes("from 'mysql2/promise'")) dependencies["mysql2"] = "^3.9.0";
|
|
21378
21435
|
if (allSource.includes("import mssql from 'mssql'")) dependencies["mssql"] = "^10.0.0";
|
|
21379
|
-
if (allSource.includes("import Database from 'better-sqlite3'")) dependencies["better-sqlite3"] =
|
|
21436
|
+
if (allSource.includes("import Database from 'better-sqlite3'")) dependencies["better-sqlite3"] = BETTER_SQLITE3_RANGE;
|
|
21380
21437
|
if (allSource.includes("from '@libsql/client'")) dependencies["@libsql/client"] = "^0.14.0";
|
|
21381
21438
|
if (allSource.includes("from '@electric-sql/pglite'")) dependencies["@electric-sql/pglite"] = "^0.5.0";
|
|
21382
21439
|
if (/from ['"]hyperformula['"]/.test(allSource)) dependencies["hyperformula"] = "^3.3.0";
|
|
@@ -21476,12 +21533,20 @@ import { ${imports.join(", ")} } from './schemas'
|
|
|
21476
21533
|
${blocks.join("\n\n")}
|
|
21477
21534
|
`;
|
|
21478
21535
|
}
|
|
21536
|
+
/** The better-sqlite3 range a generated app installs. Mirrored in the studio
|
|
21537
|
+
* package's designer-server.ts RUNTIME_DEPS. */
|
|
21538
|
+
var BETTER_SQLITE3_RANGE = "^12.1.0 || ^13.0.0";
|
|
21479
21539
|
var VITEST_CONFIG = `import { defineConfig } from 'vitest/config'
|
|
21540
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
21480
21541
|
|
|
21481
|
-
//
|
|
21482
|
-
//
|
|
21542
|
+
// Test runner for the generated smoke tests, in Node (no DOM). Kept separate from
|
|
21543
|
+
// vite.config so the SvelteKit plugin doesn't load here. The tests import the
|
|
21544
|
+
// @svgrid/enterprise root, which re-exports .svelte components: the svelte plugin
|
|
21545
|
+
// compiles them, and inlining @svgrid/* routes them through it instead of Node's
|
|
21546
|
+
// own loader (which rejects the .svelte extension).
|
|
21483
21547
|
export default defineConfig({
|
|
21484
|
-
|
|
21548
|
+
plugins: [svelte()],
|
|
21549
|
+
test: { environment: 'node', include: ['src/**/*.test.ts'], server: { deps: { inline: [/@svgrid\\//] } } },
|
|
21485
21550
|
})
|
|
21486
21551
|
`;
|
|
21487
21552
|
/**
|
|
@@ -21546,12 +21611,14 @@ jobs:
|
|
|
21546
21611
|
${steps}`
|
|
21547
21612
|
}];
|
|
21548
21613
|
}
|
|
21549
|
-
function emitStudioAppBundle(project) {
|
|
21614
|
+
function emitStudioAppBundle(project, opts = {}) {
|
|
21550
21615
|
const generated = emitStudioProject(project);
|
|
21551
21616
|
const allSource = generated.map((f) => f.contents).join("\n");
|
|
21617
|
+
const dotEnv = envDotFile(allSource, project, opts.databaseUrl);
|
|
21618
|
+
const readmeConnections = `SQL entities read their connection from \`.env\` (\`DATABASE_URL\`)${generated.some((f) => f.path === "src/lib/connections.ts") ? ";\nSupabase entities use the shared client in `src/lib/connections.ts`, which reads\n`PUBLIC_SUPABASE_URL` and `PUBLIC_SUPABASE_ANON_KEY` from `.env`" : ""}.\n`;
|
|
21552
21619
|
const plan = deployPlan(project);
|
|
21553
21620
|
const deploySteps = plan.dashboard ? `1. Push this folder to a Git repo (GitHub / GitLab / Bitbucket).\n2. Import it at <${plan.dashboard}> - build settings are detected automatically.\n\nOr deploy straight from your machine with the CLI:\n\n\`\`\`bash\n${plan.cli}\n\`\`\`` : `Build and run the server:\n\n\`\`\`bash\n${plan.cli}\n\`\`\``;
|
|
21554
|
-
const readme = `# ${project.title}\n\nGenerated with SvGrid Studio.\n\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\
|
|
21621
|
+
const readme = `# ${project.title}\n\nGenerated with SvGrid Studio.\n\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n${readmeConnections}Everything else runs on seeded in-memory data.\n\n## Deploy (${plan.label})\n\nThis app is configured for **${plan.label}** (SvelteKit \`${plan.adapterModule}\`).\n\n${deploySteps}\n\nTo target a different host, pick another **Deploy target** in the designer and\nre-generate, or swap the adapter in \`svelte.config.js\`. Entities on **Local\ndatabase** (PGlite) or **In-memory** need no server env; SQL / Supabase entities\nneed their connection set in the host's environment variables.\n\n## Round-tripping back into the designer\n\nThis app ships its own design model in \`studio.config.json\`. To keep editing\nvisually, open the SvGrid Studio designer and **Load** that file - your entities,\nscreens, blocks, theme, RBAC, i18n, etc. come back exactly as generated, and you\ncan re-generate from there.\n\nThe designer regenerates the files under \`src/routes\` and \`src/lib\` from the\nmodel, so **keep your own custom code in new files/modules and import it**, rather\nthan editing the generated screens in place - that way a re-generate never\nclobbers your work. (The CLI workflow, \`npx @svgrid/studio add\`, is the\nalternative: it wraps generated code in \`svgrid:managed\` markers and preserves\nanything you write outside them.)\n`;
|
|
21555
21622
|
const bundle = [...[
|
|
21556
21623
|
{
|
|
21557
21624
|
path: "package.json",
|
|
@@ -21582,10 +21649,10 @@ function emitStudioAppBundle(project) {
|
|
|
21582
21649
|
description: "Environment variables the app reads (copy to .env and fill in).",
|
|
21583
21650
|
contents: envExample(allSource)
|
|
21584
21651
|
}] : [],
|
|
21585
|
-
...
|
|
21652
|
+
...dotEnv ? [{
|
|
21586
21653
|
path: ".env",
|
|
21587
|
-
description: "Local env (git-ignored): a real random SESSION_SECRET
|
|
21588
|
-
contents:
|
|
21654
|
+
description: "Local env (git-ignored): a real random SESSION_SECRET, the Supabase connection the design holds and a SQLite file path are pre-filled, so the app runs as generated; fill in the rest.",
|
|
21655
|
+
contents: dotEnv
|
|
21589
21656
|
}] : [],
|
|
21590
21657
|
{
|
|
21591
21658
|
path: "src/app.css",
|
|
@@ -41261,23 +41328,64 @@ async function resolveSchemas(from, io) {
|
|
|
41261
41328
|
if (src == null) throw new Error(`runStudioAddApp: schema file not found: ${from}`);
|
|
41262
41329
|
return isPrismaSource(from, src) ? introspectPrismaAll(src) : introspectDrizzleAll(src);
|
|
41263
41330
|
}
|
|
41264
|
-
/**
|
|
41331
|
+
/**
|
|
41332
|
+
* Where to park a generated file next to `path`: `<path>.new`, unless that holds
|
|
41333
|
+
* something else (an earlier run's copy the user may be merging), then the first
|
|
41334
|
+
* free `<path>.new.1`, `.new.2`, ... A slot already holding these exact contents
|
|
41335
|
+
* is reused, so re-running does not pile up copies.
|
|
41336
|
+
*/
|
|
41337
|
+
async function freeNewPath(path, contents, io) {
|
|
41338
|
+
for (let i = 0;; i++) {
|
|
41339
|
+
const candidate = i === 0 ? `${path}.new` : `${path}.new.${i}`;
|
|
41340
|
+
const there = await io.readFile(candidate);
|
|
41341
|
+
if (there == null || there === contents) return candidate;
|
|
41342
|
+
}
|
|
41343
|
+
}
|
|
41344
|
+
/**
|
|
41345
|
+
* Merge-write a set of generated files, preserving user edits outside managed
|
|
41346
|
+
* regions.
|
|
41347
|
+
*
|
|
41348
|
+
* A file already on disk with no managed markers is somebody's own code (the
|
|
41349
|
+
* create-studio starter's nav layout, a hand-written page), and a merge would
|
|
41350
|
+
* replace it whole. Those get the new version beside them as `<file>.new`, the
|
|
41351
|
+
* same convention the designer uses for a hand-edited file, and come back in
|
|
41352
|
+
* `conflicts`. Unlike the designer, an existing `.new` with other contents is
|
|
41353
|
+
* kept: the copy goes to `.new.1` and up (see freeNewPath). A folder `init` generated (it has studio.config.json) is the
|
|
41354
|
+
* exception: its files are Studio's own output, and regenerating them is the
|
|
41355
|
+
* point.
|
|
41356
|
+
*/
|
|
41265
41357
|
async function writeAll$1(files, io) {
|
|
41266
41358
|
const written = [];
|
|
41359
|
+
const conflicts = [];
|
|
41360
|
+
const studioApp = await io.readFile("studio.config.json") != null;
|
|
41267
41361
|
for (const file of files) {
|
|
41268
41362
|
const existing = await io.readFile(file.path);
|
|
41269
41363
|
if (skipUserOwned(file, existing != null)) continue;
|
|
41364
|
+
if (existing != null && !studioApp && existing !== file.contents && !existing.includes("// svgrid:managed:start")) {
|
|
41365
|
+
const parkedAs = await freeNewPath(file.path, file.contents, io);
|
|
41366
|
+
await io.writeFile(parkedAs, file.contents);
|
|
41367
|
+
conflicts.push({
|
|
41368
|
+
path: file.path,
|
|
41369
|
+
parkedAs
|
|
41370
|
+
});
|
|
41371
|
+
continue;
|
|
41372
|
+
}
|
|
41270
41373
|
await io.writeFile(file.path, mergeManaged(existing, file.contents));
|
|
41271
41374
|
written.push(file.path);
|
|
41272
41375
|
}
|
|
41273
|
-
return
|
|
41376
|
+
return {
|
|
41377
|
+
written,
|
|
41378
|
+
conflicts
|
|
41379
|
+
};
|
|
41274
41380
|
}
|
|
41275
41381
|
async function runStudioAdd(options, io) {
|
|
41276
41382
|
const schema = await resolveSchema(options, io);
|
|
41277
41383
|
const { files } = scaffold(schema, options);
|
|
41384
|
+
const { written, conflicts } = await writeAll$1(files, io);
|
|
41278
41385
|
return {
|
|
41279
41386
|
schema,
|
|
41280
|
-
written
|
|
41387
|
+
written,
|
|
41388
|
+
conflicts,
|
|
41281
41389
|
verify: await verifyScaffold(files)
|
|
41282
41390
|
};
|
|
41283
41391
|
}
|
|
@@ -41326,8 +41434,10 @@ async function appShellDefaults(options, io) {
|
|
|
41326
41434
|
*/
|
|
41327
41435
|
async function runStudioAddApp(schemas, options, io) {
|
|
41328
41436
|
const { files } = scaffoldApp(schemas, await appShellDefaults(options, io));
|
|
41437
|
+
const { written, conflicts } = await writeAll$1(files, io);
|
|
41329
41438
|
return {
|
|
41330
|
-
written
|
|
41439
|
+
written,
|
|
41440
|
+
conflicts,
|
|
41331
41441
|
verify: await verifyScaffold(files)
|
|
41332
41442
|
};
|
|
41333
41443
|
}
|
|
@@ -41423,29 +41533,41 @@ async function viaSample(o, base) {
|
|
|
41423
41533
|
}));
|
|
41424
41534
|
}
|
|
41425
41535
|
/** List a Supabase project's tables from its PostgREST OpenAPI doc (URL + anon key).
|
|
41426
|
-
* Returns the exposed table names (sorted)
|
|
41427
|
-
*
|
|
41428
|
-
*
|
|
41536
|
+
* Returns the exposed table names (sorted). `[]` means the project answered and
|
|
41537
|
+
* lists none, or answered 401/403 without naming the key as invalid (the doc is
|
|
41538
|
+
* restricted for this key; each table can still be introspected by name).
|
|
41539
|
+
* Throws, naming the URL, when the project cannot be reached (wrong URL, CORS),
|
|
41540
|
+
* answers with another error status, says the key is invalid, or sends something
|
|
41541
|
+
* that is not an API description. */
|
|
41429
41542
|
async function listSupabaseTables(url, key, fetchFn = globalThis.fetch) {
|
|
41430
|
-
const
|
|
41543
|
+
const at = `${url.replace(/\/+$/, "")}/rest/v1/`;
|
|
41431
41544
|
let res;
|
|
41432
41545
|
try {
|
|
41433
|
-
res = await fetchFn(
|
|
41546
|
+
res = await fetchFn(at, { headers: {
|
|
41434
41547
|
apikey: key,
|
|
41435
41548
|
Authorization: `Bearer ${key}`,
|
|
41436
41549
|
Accept: "application/json"
|
|
41437
41550
|
} });
|
|
41438
|
-
} catch {
|
|
41439
|
-
|
|
41551
|
+
} catch (e) {
|
|
41552
|
+
throw new Error(`Could not reach ${at} (${e instanceof Error ? e.message : String(e)})`, { cause: e });
|
|
41553
|
+
}
|
|
41554
|
+
if (!res.ok) {
|
|
41555
|
+
let body = "";
|
|
41556
|
+
try {
|
|
41557
|
+
body = await res.text();
|
|
41558
|
+
} catch {}
|
|
41559
|
+
const badKey = /invalid api key/i.test(body);
|
|
41560
|
+
if ((res.status === 401 || res.status === 403) && !badKey) return [];
|
|
41561
|
+
throw new Error(`${at} answered ${res.status}${badKey ? " (invalid API key)" : ""}`);
|
|
41440
41562
|
}
|
|
41441
|
-
if (!res.ok) return [];
|
|
41442
41563
|
let spec;
|
|
41443
41564
|
try {
|
|
41444
41565
|
spec = await res.json();
|
|
41445
41566
|
} catch {
|
|
41446
|
-
|
|
41567
|
+
spec = null;
|
|
41447
41568
|
}
|
|
41448
|
-
|
|
41569
|
+
if (!spec || typeof spec !== "object" || !(spec.definitions || spec.components || spec.paths)) throw new Error(`${at} did not answer with an API description: is this a Supabase project URL?`);
|
|
41570
|
+
const defs = spec.definitions ?? spec.components?.schemas ?? {};
|
|
41449
41571
|
return Object.keys(defs).sort((a, b) => a.localeCompare(b));
|
|
41450
41572
|
}
|
|
41451
41573
|
/** Introspect a Supabase/PostgREST table into an EntitySchema, or null. */
|
|
@@ -41457,7 +41579,11 @@ async function introspectSupabaseTable(options) {
|
|
|
41457
41579
|
const base = o.url.replace(/\/+$/, "");
|
|
41458
41580
|
const columns = await viaOpenApi(o, base) ?? await viaSample(o, base) ?? await viaCsv(o, base);
|
|
41459
41581
|
if (!columns || !columns.length) return null;
|
|
41460
|
-
|
|
41582
|
+
const schema = buildEntitySchema(o.table, columns);
|
|
41583
|
+
return {
|
|
41584
|
+
...schema,
|
|
41585
|
+
fields: schema.fields.map((f) => hideBaseOrderColumn(f))
|
|
41586
|
+
};
|
|
41461
41587
|
}
|
|
41462
41588
|
//#endregion
|
|
41463
41589
|
//#region src/studio/user-error.ts
|
|
@@ -41719,7 +41845,8 @@ async function gatherFromDatabase(flags, prompts, db) {
|
|
|
41719
41845
|
schemas,
|
|
41720
41846
|
sources,
|
|
41721
41847
|
seed: {},
|
|
41722
|
-
kind: "sql"
|
|
41848
|
+
kind: "sql",
|
|
41849
|
+
...isFileDialect(dialect) ? { sqliteFile: url } : {}
|
|
41723
41850
|
};
|
|
41724
41851
|
}
|
|
41725
41852
|
/**
|
|
@@ -41896,6 +42023,24 @@ async function askTheme(flags, prompts) {
|
|
|
41896
42023
|
}
|
|
41897
42024
|
/** Prefix a path with the output folder (`.` writes into the current one). */
|
|
41898
42025
|
var inDir = (outDir, path) => (!outDir || outDir === "." ? "" : outDir.replace(/[\\/]+$/, "") + "/") + path;
|
|
42026
|
+
/**
|
|
42027
|
+
* A file path given relative to where init ran, re-expressed relative to the
|
|
42028
|
+
* app folder, which is where the app runs (`npm run dev` in `outDir`). Absolute
|
|
42029
|
+
* paths pass through. An absolute or `..` out dir has no relative answer without
|
|
42030
|
+
* the cwd, so the path is made absolute when the runtime has one.
|
|
42031
|
+
*/
|
|
42032
|
+
function fileFromApp(file, outDir) {
|
|
42033
|
+
const fileUrl = /^file:(?![\\/])(.+)$/.exec(file);
|
|
42034
|
+
if (fileUrl) return `file:${fileFromApp(fileUrl[1], outDir)}`;
|
|
42035
|
+
if (!outDir || outDir === "." || file === ":memory:" || /^([a-zA-Z]:[\\/]|[\\/]|file:)/.test(file)) return file;
|
|
42036
|
+
const rel = file.replace(/^\.[\\/]/, "");
|
|
42037
|
+
const segments = outDir.split(/[\\/]+/).filter((s) => s && s !== ".");
|
|
42038
|
+
if (/^([a-zA-Z]:[\\/]|[\\/])/.test(outDir) || segments.includes("..")) {
|
|
42039
|
+
const cwd = globalThis.process?.cwd?.();
|
|
42040
|
+
return cwd ? `${cwd.replace(/\\/g, "/").replace(/\/+$/, "")}/${rel.replace(/\\/g, "/")}` : file;
|
|
42041
|
+
}
|
|
42042
|
+
return "../".repeat(segments.length) + rel;
|
|
42043
|
+
}
|
|
41899
42044
|
/** Which marker files are present in `outDir` right now. */
|
|
41900
42045
|
async function markersIn(outDir, io) {
|
|
41901
42046
|
const found = /* @__PURE__ */ new Set();
|
|
@@ -41965,7 +42110,7 @@ async function runStudioInit(flags, prompts, db, io, fetchText = null) {
|
|
|
41965
42110
|
...theme ? { theme } : {}
|
|
41966
42111
|
});
|
|
41967
42112
|
const project = flags.kit === 3 ? setKitVersion(built, 3) : built;
|
|
41968
|
-
const written = await writeAll(emitStudioAppBundle(project), outDir, io);
|
|
42113
|
+
const written = await writeAll(emitStudioAppBundle(project, data.sqliteFile ? { databaseUrl: fileFromApp(data.sqliteFile, outDir) } : {}), outDir, io);
|
|
41969
42114
|
const configPath = inDir(outDir, "studio.config.json");
|
|
41970
42115
|
await io.writeFile(configPath, serializeProject(project));
|
|
41971
42116
|
written.push(configPath);
|
package/dist/sheet-enable.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register the keymap and the formula-aware fill, without the spreadsheet
|
|
3
|
+
* nudge. {@link installEnterprise} calls this: an app that wires the Pro API
|
|
4
|
+
* for export or pivot has not opened a spreadsheet, and must not get a card
|
|
5
|
+
* naming one.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerSheetKeymap(): void;
|
|
1
8
|
/**
|
|
2
9
|
* Register the Excel keymap. Idempotent - safe to call from every component
|
|
3
|
-
* that wants it, or once at app start.
|
|
4
|
-
* so wiring the Pro API turns the shortcuts on too.
|
|
10
|
+
* that wants it, or once at app start.
|
|
5
11
|
*/
|
|
6
12
|
export declare function enableSheet(): void;
|
package/dist/sheet-enable.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* The grid ships the `registerGridShortcuts` seam for free and interprets a key
|
|
8
8
|
* itself only when nothing claims it; the commands are here. Evaluated like
|
|
9
9
|
* the rest of Enterprise, and part of the Suite edition: it works without a
|
|
10
|
-
* license key,
|
|
11
|
-
*
|
|
10
|
+
* license key, but the grid shows the unlicensed watermark and a one-time
|
|
11
|
+
* console nudge.
|
|
12
12
|
*
|
|
13
13
|
* ```ts
|
|
14
14
|
* import { setLicenseKey, enableSheet } from '@svgrid/enterprise'
|
|
@@ -24,16 +24,17 @@ import { nudgeEnterpriseFor } from './license.js';
|
|
|
24
24
|
import { handleSheetKey } from './sheet/shortcuts.js';
|
|
25
25
|
import { setFillTranslator } from './sheet/commands.js';
|
|
26
26
|
import { translateFormula } from './sheet/refs.js';
|
|
27
|
-
let
|
|
27
|
+
let registered = false;
|
|
28
28
|
/**
|
|
29
|
-
* Register the
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Register the keymap and the formula-aware fill, without the spreadsheet
|
|
30
|
+
* nudge. {@link installEnterprise} calls this: an app that wires the Pro API
|
|
31
|
+
* for export or pivot has not opened a spreadsheet, and must not get a card
|
|
32
|
+
* naming one.
|
|
32
33
|
*/
|
|
33
|
-
export function
|
|
34
|
-
if (
|
|
34
|
+
export function registerSheetKeymap() {
|
|
35
|
+
if (registered)
|
|
35
36
|
return;
|
|
36
|
-
|
|
37
|
+
registered = true;
|
|
37
38
|
// Priority 100 so the sheet keymap sits above anything an app registers of
|
|
38
39
|
// its own without asking for a priority.
|
|
39
40
|
registerGridShortcuts(handleSheetKey, { id: 'svgrid-sheet', priority: 100 });
|
|
@@ -41,5 +42,16 @@ export function enableSheet() {
|
|
|
41
42
|
// literal text into every row; with it the relative half of each reference
|
|
42
43
|
// shifts and the absolute half does not, the way Excel fills.
|
|
43
44
|
setFillTranslator((value, delta) => translateFormula(value, delta.rows, delta.cols));
|
|
45
|
+
}
|
|
46
|
+
let enabled = false;
|
|
47
|
+
/**
|
|
48
|
+
* Register the Excel keymap. Idempotent - safe to call from every component
|
|
49
|
+
* that wants it, or once at app start.
|
|
50
|
+
*/
|
|
51
|
+
export function enableSheet() {
|
|
52
|
+
registerSheetKeymap();
|
|
53
|
+
if (enabled)
|
|
54
|
+
return;
|
|
55
|
+
enabled = true;
|
|
44
56
|
nudgeEnterpriseFor('spreadsheet', 'Spreadsheet');
|
|
45
57
|
}
|
|
@@ -22,9 +22,12 @@ export type IntrospectSupabaseOptions = {
|
|
|
22
22
|
fetch?: typeof fetch;
|
|
23
23
|
};
|
|
24
24
|
/** List a Supabase project's tables from its PostgREST OpenAPI doc (URL + anon key).
|
|
25
|
-
* Returns the exposed table names (sorted)
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Returns the exposed table names (sorted). `[]` means the project answered and
|
|
26
|
+
* lists none, or answered 401/403 without naming the key as invalid (the doc is
|
|
27
|
+
* restricted for this key; each table can still be introspected by name).
|
|
28
|
+
* Throws, naming the URL, when the project cannot be reached (wrong URL, CORS),
|
|
29
|
+
* answers with another error status, says the key is invalid, or sends something
|
|
30
|
+
* that is not an API description. */
|
|
28
31
|
export declare function listSupabaseTables(url: string, key: string, fetchFn?: typeof fetch): Promise<string[]>;
|
|
29
32
|
/** Introspect a Supabase/PostgREST table into an EntitySchema, or null. */
|
|
30
33
|
export declare function introspectSupabaseTable<TData extends RowData = RowData>(options: IntrospectSupabaseOptions): Promise<EntitySchema<TData> | null>;
|