@svgrid/enterprise 3.1.0 → 3.1.1
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/SvSheet.svelte +87 -8
- package/dist/cdn/svgrid-enterprise.svelte-external.js +13269 -13209
- package/dist/designer/assets/{GridMenus-8gTNssgV.js → GridMenus-DGZJKFvX.js} +1 -1
- package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +1 -0
- package/dist/designer/assets/{SvGridCellEditor-DMoWJjEI.js → SvGridCellEditor-C7NWcnNC.js} +1 -1
- package/dist/designer/assets/{SvGridChart-Cy1ATrWh.js → SvGridChart-Fg43Yi0M.js} +2 -2
- package/dist/designer/assets/{SvGridChartBuilder-eQ3mn0L9.js → SvGridChartBuilder-BqGuA3yU.js} +1 -1
- package/dist/designer/assets/{SvGridChartPanel-CATdVc9X.js → SvGridChartPanel-BsiXRAnx.js} +3 -3
- package/dist/designer/assets/{SvGridChartView-CmsZtCED.js → SvGridChartView-BgttK0tR.js} +1 -1
- package/dist/designer/assets/{SvListBox-BDS66lmZ.js → SvListBox-v2jm0-vf.js} +1 -1
- package/dist/designer/assets/cell-formatting-BWrW_O0n.js +1 -0
- package/dist/designer/assets/{chart-Cn7MnNqA.js → chart-BIl-dIRv.js} +1 -1
- package/dist/designer/assets/{chart-panel-messages-Dz7PTIqT.js → chart-panel-messages-4cF2vw7L.js} +1 -1
- package/dist/designer/assets/{chart-summary-BD1qYB2R.js → chart-summary-B7bTtSCC.js} +1 -1
- package/dist/designer/assets/{chart-trend-B8xr5LuD.js → chart-trend-CLsn5Hgd.js} +1 -1
- package/dist/designer/assets/export-format-DGscOjS9.js +6 -0
- package/dist/designer/assets/{index-BSjv6Qec.js → index-DkhD2kIe.js} +314 -314
- package/dist/designer/assets/index-l21iGkpF.css +1 -0
- package/dist/designer/assets/src-Mu7AGqT8.js +58 -0
- package/dist/designer/index.html +12 -12
- package/dist/export-ooxml.d.ts +10 -0
- package/dist/export-ooxml.js +22 -5
- package/dist/import.js +11 -2
- package/dist/node/studio.js +221 -129
- package/dist/sheet/objects.d.ts +47 -0
- package/dist/sheet/objects.js +33 -0
- package/dist/studio/cli.js +55 -1
- package/dist/studio/init-flow.d.ts +6 -1
- package/dist/studio/init-flow.js +43 -2
- package/dist/studio/scaffold-app.d.ts +6 -0
- package/dist/studio/scaffold-app.js +5 -3
- package/dist/studio/scaffold.js +29 -12
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wc/{GridMenus-By9vPuq0.js → GridMenus-6_fF5jIm.js} +2 -2
- package/dist/wc/{SvGrid.helpers-BdVDtjUm.js → SvGrid.helpers-zBkDTryR.js} +36 -37
- package/dist/wc/{SvGridCellEditor-Bnf1Kr5C.js → SvGridCellEditor-CzUKEbkI.js} +1 -1
- package/dist/wc/{SvGridChart-BqUJh2pG.js → SvGridChart-C6UvNIbN.js} +3 -3
- package/dist/wc/{SvGridChartBuilder-CGycC5cn.js → SvGridChartBuilder-ikf_5LyW.js} +3 -3
- package/dist/wc/{SvGridChartPanel-CRnizsHD.js → SvGridChartPanel-Dc_jj7B-.js} +4 -4
- package/dist/wc/{SvGridChartView-D1pa_xMy.js → SvGridChartView-thkznAOb.js} +2 -2
- package/dist/wc/{cell-formatting-CBfgahv1.js → cell-formatting-hb3vzxhJ.js} +27 -5
- package/dist/wc/{chart-6OXlybVT.js → chart-CV5D7_k8.js} +1 -1
- package/dist/wc/{chart-summary-2m3ia1-n.js → chart-summary-DGx0QhJa.js} +1 -1
- package/dist/wc/{chart-trend-CQOQtTMl.js → chart-trend-BIRH_xH8.js} +1 -1
- package/dist/wc/sv-sheet-element.js +7444 -7347
- package/dist/wc/{svelte-virtualizer.svelte-CXtQy2TQ.js → svelte-virtualizer.svelte-Du4tjrIP.js} +7 -3
- package/package.json +3 -3
- package/dist/designer/assets/SvGrid.helpers-BgT-gnCo.js +0 -1
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +0 -1
- package/dist/designer/assets/export-format-DGckmgKr.js +0 -6
- package/dist/designer/assets/index-_JVFxt02.css +0 -1
- package/dist/designer/assets/src-DUQ4xE3x.js +0 -58
package/dist/node/studio.js
CHANGED
|
@@ -1721,7 +1721,8 @@ ${managed$1(serverBody)}
|
|
|
1721
1721
|
const pageBody = ` const source = createKitDataSource<${rowType}>({ endpoint: '${apiRoute}' })
|
|
1722
1722
|
const columns = schemaToColumns(${constName})${hasLookups ? `\n\n${lookupDecls}\n${lookupsMap}` : ""}
|
|
1723
1723
|
|
|
1724
|
-
|
|
1724
|
+
// Not "state": a local of that name makes svelte-check read $state as a store of it.
|
|
1725
|
+
let view = $state<ServerState<${rowType}>>({
|
|
1725
1726
|
rows: [], total: 0, loading: false, saving: false, error: null,
|
|
1726
1727
|
pageIndex: 0, pageSize: 25, pageCount: 1, sortModel: [], filterModel: {},
|
|
1727
1728
|
})
|
|
@@ -1729,7 +1730,7 @@ ${managed$1(serverBody)}
|
|
|
1729
1730
|
pageSize: 25,
|
|
1730
1731
|
optimistic: true,
|
|
1731
1732
|
getRowId: (r) => String(r.${idField}),
|
|
1732
|
-
onChange: (s) => (
|
|
1733
|
+
onChange: (s) => (view = s),
|
|
1733
1734
|
})
|
|
1734
1735
|
controller.refresh()
|
|
1735
1736
|
|
|
@@ -1739,7 +1740,7 @@ ${managed$1(serverBody)}
|
|
|
1739
1740
|
async function save({ mode, id, values }: { mode: 'create' | 'edit'; id: string | null; values: Partial<${rowType}> }) {
|
|
1740
1741
|
if (mode === 'create') {
|
|
1741
1742
|
await controller.createRow(values)
|
|
1742
|
-
controller.setPage(
|
|
1743
|
+
controller.setPage(view.pageCount - 1) // jump to the new row (appended at the end)
|
|
1743
1744
|
} else if (id) {
|
|
1744
1745
|
await controller.updateRow(id, values)
|
|
1745
1746
|
}
|
|
@@ -1757,12 +1758,11 @@ ${managed$1(serverBody)}
|
|
|
1757
1758
|
path: `src/routes/${route}/+page.svelte`,
|
|
1758
1759
|
description: "The data screen: sort, filter, search, paging, multi-select delete, and create/edit - wired to the API route.",
|
|
1759
1760
|
contents: `<script lang="ts">
|
|
1760
|
-
import { SvGrid, createServerDataSource } from '@svgrid/grid'
|
|
1761
|
+
import { SvGrid, createServerDataSource, type ServerState } from '@svgrid/grid'
|
|
1761
1762
|
import {
|
|
1762
1763
|
SvGridEditPanel,
|
|
1763
1764
|
createKitDataSource,${hasLookups ? "\n createRelationLookup," : ""}
|
|
1764
1765
|
schemaToColumns,
|
|
1765
|
-
type ServerState,
|
|
1766
1766
|
} from '@svgrid/enterprise'
|
|
1767
1767
|
import { ${constName}, type ${rowType} } from '${schemaImport}'
|
|
1768
1768
|
|
|
@@ -1787,12 +1787,12 @@ ${themeTokenCss({
|
|
|
1787
1787
|
</div>
|
|
1788
1788
|
</header>
|
|
1789
1789
|
|
|
1790
|
-
{#if
|
|
1790
|
+
{#if view.error}<p class="page__error" role="alert">{view.error}</p>{/if}
|
|
1791
1791
|
|
|
1792
1792
|
<SvGrid
|
|
1793
|
-
data={
|
|
1793
|
+
data={view.rows}
|
|
1794
1794
|
{columns}
|
|
1795
|
-
loading={
|
|
1795
|
+
loading={view.loading}
|
|
1796
1796
|
loadingOverlay
|
|
1797
1797
|
fitColumns
|
|
1798
1798
|
enableRowSummaries={false}
|
|
@@ -1815,11 +1815,11 @@ ${themeTokenCss({
|
|
|
1815
1815
|
onRowSelectionChange={(_sel, rows) => (selected = rows)}
|
|
1816
1816
|
showPagination
|
|
1817
1817
|
externalPagination
|
|
1818
|
-
rowCount={
|
|
1819
|
-
pageIndex={
|
|
1820
|
-
pageSize={
|
|
1818
|
+
rowCount={view.total}
|
|
1819
|
+
pageIndex={view.pageIndex}
|
|
1820
|
+
pageSize={view.pageSize}
|
|
1821
1821
|
onPaginationChange={({ pageIndex, pageSize }) => {
|
|
1822
|
-
if (pageSize !==
|
|
1822
|
+
if (pageSize !== view.pageSize) controller.setPageSize(pageSize)
|
|
1823
1823
|
else controller.setPage(pageIndex)
|
|
1824
1824
|
}}
|
|
1825
1825
|
emptyMessage="No ${label} yet"
|
|
@@ -1851,6 +1851,7 @@ ${themeTokenCss({
|
|
|
1851
1851
|
*/
|
|
1852
1852
|
function mergeManaged(existing, generated) {
|
|
1853
1853
|
if (!existing) return generated;
|
|
1854
|
+
existing = migrateStateLocal(existing);
|
|
1854
1855
|
const startIdx = existing.indexOf(MANAGED_START);
|
|
1855
1856
|
const endIdx = existing.indexOf(MANAGED_END);
|
|
1856
1857
|
if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) return generated;
|
|
@@ -1860,6 +1861,21 @@ function mergeManaged(existing, generated) {
|
|
|
1860
1861
|
const newRegion = generated.slice(genStart, genEnd + 21);
|
|
1861
1862
|
return existing.slice(0, startIdx) + newRegion + existing.slice(endIdx + 21);
|
|
1862
1863
|
}
|
|
1864
|
+
/** The managed line pages scaffolded before the rename carried. */
|
|
1865
|
+
var OLD_STATE_LOCAL = /\blet state = \$state<ServerState</;
|
|
1866
|
+
/**
|
|
1867
|
+
* Pages scaffolded before the rename kept their server state in a local named
|
|
1868
|
+
* `state`, which svelte-check reads as a store subscription whenever `$state`
|
|
1869
|
+
* appears. The managed region now calls it `view`, but the markup below the
|
|
1870
|
+
* region is the user's and still says `state.rows`; a plain merge would leave it
|
|
1871
|
+
* pointing at nothing. Rename the references too. Nothing else in such a page
|
|
1872
|
+
* could be called `state` (the old local would have clashed), and member reads
|
|
1873
|
+
* like `page.state` are skipped.
|
|
1874
|
+
*/
|
|
1875
|
+
function migrateStateLocal(existing) {
|
|
1876
|
+
if (!OLD_STATE_LOCAL.test(existing)) return existing;
|
|
1877
|
+
return existing.replace(/(?<![.\w$])state(?=\s*[.?[]|\s*=[^=>])/g, "view");
|
|
1878
|
+
}
|
|
1863
1879
|
//#endregion
|
|
1864
1880
|
//#region src/studio/scaffold-app.ts
|
|
1865
1881
|
/**
|
|
@@ -1875,12 +1891,12 @@ function mergeManaged(existing, generated) {
|
|
|
1875
1891
|
*/
|
|
1876
1892
|
var MANAGED_NOTE = "// Regenerated by SvGrid Studio. Edits inside these markers are overwritten.";
|
|
1877
1893
|
var managed = (body) => `${MANAGED_START}\n${MANAGED_NOTE}\n${body}\n${MANAGED_END}`;
|
|
1878
|
-
function layoutFile$1(nav, appTitle, theme) {
|
|
1894
|
+
function layoutFile$1(nav, appTitle, theme, stylesheets) {
|
|
1879
1895
|
return {
|
|
1880
1896
|
path: "src/routes/+layout.svelte",
|
|
1881
1897
|
description: "App shell: a collapsible nav sidebar linking every entity screen (an off-canvas drawer on tablet/phone), with the routed page in the main area.",
|
|
1882
1898
|
contents: `<script lang="ts">
|
|
1883
|
-
${managed(` let { children } = $props()
|
|
1899
|
+
${managed(`${stylesheets.map((css) => ` import '../${css}'\n`).join("")} let { children } = $props()
|
|
1884
1900
|
const appTitle = ${JSON.stringify(appTitle)}
|
|
1885
1901
|
const nav = ${JSON.stringify(nav)}
|
|
1886
1902
|
|
|
@@ -2003,10 +2019,11 @@ function scaffoldApp(schemas, options = {}) {
|
|
|
2003
2019
|
href: `/${s.name}`,
|
|
2004
2020
|
label: s.label ?? titleCase$2(s.name)
|
|
2005
2021
|
}));
|
|
2006
|
-
|
|
2022
|
+
const theme = {
|
|
2007
2023
|
preset: options.theme,
|
|
2008
2024
|
mode: options.dark ? "dark" : "light"
|
|
2009
|
-
}
|
|
2025
|
+
};
|
|
2026
|
+
files.push(layoutFile$1(nav, appTitle, theme, options.stylesheets ?? []), homeFile$1(nav, appTitle));
|
|
2010
2027
|
return { files };
|
|
2011
2028
|
}
|
|
2012
2029
|
//#endregion
|
|
@@ -16028,7 +16045,7 @@ function hasFieldConditions(schema) {
|
|
|
16028
16045
|
*
|
|
16029
16046
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
16030
16047
|
*/
|
|
16031
|
-
var SVGRID_VERSION = "3.1.
|
|
16048
|
+
var SVGRID_VERSION = "3.1.1";
|
|
16032
16049
|
//#endregion
|
|
16033
16050
|
//#region src/studio/emit-project.ts
|
|
16034
16051
|
var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
|
|
@@ -40702,6 +40719,153 @@ function getStarterDataset(id) {
|
|
|
40702
40719
|
return starterDatasets.find((d) => d.id === id);
|
|
40703
40720
|
}
|
|
40704
40721
|
//#endregion
|
|
40722
|
+
//#region src/studio/verify.ts
|
|
40723
|
+
async function verifyScaffold(files) {
|
|
40724
|
+
const issues = [];
|
|
40725
|
+
let checked = 0;
|
|
40726
|
+
let skipped = 0;
|
|
40727
|
+
let compile;
|
|
40728
|
+
try {
|
|
40729
|
+
compile = (await import("svelte/compiler")).compile;
|
|
40730
|
+
} catch {
|
|
40731
|
+
compile = void 0;
|
|
40732
|
+
}
|
|
40733
|
+
for (const file of files) {
|
|
40734
|
+
if (!file.path.endsWith(".svelte") || !compile) {
|
|
40735
|
+
skipped++;
|
|
40736
|
+
continue;
|
|
40737
|
+
}
|
|
40738
|
+
try {
|
|
40739
|
+
const { warnings } = compile(file.contents, {
|
|
40740
|
+
filename: file.path,
|
|
40741
|
+
generate: "client"
|
|
40742
|
+
});
|
|
40743
|
+
checked++;
|
|
40744
|
+
for (const w of warnings) issues.push({
|
|
40745
|
+
path: file.path,
|
|
40746
|
+
severity: "warning",
|
|
40747
|
+
message: w.message ?? "warning"
|
|
40748
|
+
});
|
|
40749
|
+
} catch (err) {
|
|
40750
|
+
checked++;
|
|
40751
|
+
issues.push({
|
|
40752
|
+
path: file.path,
|
|
40753
|
+
severity: "error",
|
|
40754
|
+
message: err instanceof Error ? err.message : String(err)
|
|
40755
|
+
});
|
|
40756
|
+
}
|
|
40757
|
+
}
|
|
40758
|
+
return {
|
|
40759
|
+
ok: !issues.some((i) => i.severity === "error"),
|
|
40760
|
+
issues,
|
|
40761
|
+
checked,
|
|
40762
|
+
skipped
|
|
40763
|
+
};
|
|
40764
|
+
}
|
|
40765
|
+
/** One-line human summary of a verify result, for tool output / logs. */
|
|
40766
|
+
function summarizeVerify(result) {
|
|
40767
|
+
if (result.checked === 0) return "verification skipped (Svelte compiler unavailable) - run svelte-check in your project.";
|
|
40768
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
40769
|
+
if (errors.length === 0) return `verified: ${result.checked} file(s) compile.`;
|
|
40770
|
+
return `VERIFICATION FAILED (${errors.length} error(s)):\n` + errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
|
|
40771
|
+
}
|
|
40772
|
+
//#endregion
|
|
40773
|
+
//#region src/studio/cli.ts
|
|
40774
|
+
/** A `.prisma` file (by extension or a `model X {` block) parses as Prisma; otherwise Drizzle. */
|
|
40775
|
+
function isPrismaSource(path, source) {
|
|
40776
|
+
return /\.prisma$/i.test(path) || /\bmodel\s+\w+\s*\{/.test(source);
|
|
40777
|
+
}
|
|
40778
|
+
/** Resolve an EntitySchema from whichever input was provided. */
|
|
40779
|
+
async function resolveSchema(options, io) {
|
|
40780
|
+
if (options.schema) return options.schema;
|
|
40781
|
+
if (options.from) {
|
|
40782
|
+
const src = await io.readFile(options.from);
|
|
40783
|
+
if (src == null) throw new Error(`runStudioAdd: schema file not found: ${options.from}`);
|
|
40784
|
+
return isPrismaSource(options.from, src) ? introspectPrisma(src, options.table) : introspectDrizzle(src, options.table);
|
|
40785
|
+
}
|
|
40786
|
+
if (options.rows) return introspectJson(options.name ?? options.route ?? "entity", options.rows);
|
|
40787
|
+
throw new Error("runStudioAdd: provide `from` (a Drizzle or Prisma schema file), `rows`, or `schema`");
|
|
40788
|
+
}
|
|
40789
|
+
/**
|
|
40790
|
+
* Resolve **every** entity from a Drizzle or Prisma schema file (the `--all
|
|
40791
|
+
* --from` path), linked so foreign keys become working relation lookups.
|
|
40792
|
+
*/
|
|
40793
|
+
async function resolveSchemas(from, io) {
|
|
40794
|
+
const src = await io.readFile(from);
|
|
40795
|
+
if (src == null) throw new Error(`runStudioAddApp: schema file not found: ${from}`);
|
|
40796
|
+
return isPrismaSource(from, src) ? introspectPrismaAll(src) : introspectDrizzleAll(src);
|
|
40797
|
+
}
|
|
40798
|
+
/** Merge-write a set of generated files, preserving user edits outside managed regions. */
|
|
40799
|
+
async function writeAll$1(files, io) {
|
|
40800
|
+
const written = [];
|
|
40801
|
+
for (const file of files) {
|
|
40802
|
+
const existing = await io.readFile(file.path);
|
|
40803
|
+
if (skipUserOwned(file, existing != null)) continue;
|
|
40804
|
+
await io.writeFile(file.path, mergeManaged(existing, file.contents));
|
|
40805
|
+
written.push(file.path);
|
|
40806
|
+
}
|
|
40807
|
+
return written;
|
|
40808
|
+
}
|
|
40809
|
+
async function runStudioAdd(options, io) {
|
|
40810
|
+
const schema = await resolveSchema(options, io);
|
|
40811
|
+
const { files } = scaffold(schema, options);
|
|
40812
|
+
return {
|
|
40813
|
+
schema,
|
|
40814
|
+
written: await writeAll$1(files, io),
|
|
40815
|
+
verify: await verifyScaffold(files)
|
|
40816
|
+
};
|
|
40817
|
+
}
|
|
40818
|
+
/** The stylesheets an `init`-generated layout imports, in cascade order. */
|
|
40819
|
+
var APP_STYLESHEETS = ["app.css", "custom.css"];
|
|
40820
|
+
/**
|
|
40821
|
+
* Fill the app-shell options the caller left out from the app already on disk,
|
|
40822
|
+
* so `add --all` in a folder `init` generated keeps its stylesheets, title and
|
|
40823
|
+
* theme. The shell layout from `init` carries no managed markers, so the merge
|
|
40824
|
+
* write replaces it whole; anything it imported or named has to be carried
|
|
40825
|
+
* over here or it is lost.
|
|
40826
|
+
*
|
|
40827
|
+
* Title: studio.config.json (shell brand, then title), else the `appTitle`
|
|
40828
|
+
* constant of a layout an earlier `add --all` wrote.
|
|
40829
|
+
*/
|
|
40830
|
+
async function appShellDefaults(options, io) {
|
|
40831
|
+
const stylesheets = options.stylesheets ?? [];
|
|
40832
|
+
if (!options.stylesheets) {
|
|
40833
|
+
for (const css of APP_STYLESHEETS) if (await io.readFile(`src/${css}`) != null) stylesheets.push(css);
|
|
40834
|
+
}
|
|
40835
|
+
let title;
|
|
40836
|
+
let theme;
|
|
40837
|
+
const config = await io.readFile("studio.config.json");
|
|
40838
|
+
if (config != null) try {
|
|
40839
|
+
const parsed = JSON.parse(config);
|
|
40840
|
+
theme = parsed.theme;
|
|
40841
|
+
title = (typeof theme?.shell?.brand === "string" ? theme.shell.brand.trim() : "") || (typeof parsed.title === "string" ? parsed.title : void 0);
|
|
40842
|
+
} catch {}
|
|
40843
|
+
if (!title) {
|
|
40844
|
+
const m = (await io.readFile("src/routes/+layout.svelte"))?.match(/const appTitle = ("(?:[^"\\]|\\.)*")/);
|
|
40845
|
+
if (m) try {
|
|
40846
|
+
title = JSON.parse(m[1]);
|
|
40847
|
+
} catch {}
|
|
40848
|
+
}
|
|
40849
|
+
return {
|
|
40850
|
+
...options,
|
|
40851
|
+
stylesheets,
|
|
40852
|
+
...options.appTitle == null && title ? { appTitle: title } : {},
|
|
40853
|
+
...options.theme == null && typeof theme?.preset === "string" ? { theme: theme.preset } : {},
|
|
40854
|
+
...options.dark == null && theme?.mode === "dark" ? { dark: true } : {}
|
|
40855
|
+
};
|
|
40856
|
+
}
|
|
40857
|
+
/**
|
|
40858
|
+
* Scaffold a whole multi-entity app - every entity's screen plus a nav layout
|
|
40859
|
+
* and home page - and merge-write it. The `--all` path behind `svgrid-studio`.
|
|
40860
|
+
*/
|
|
40861
|
+
async function runStudioAddApp(schemas, options, io) {
|
|
40862
|
+
const { files } = scaffoldApp(schemas, await appShellDefaults(options, io));
|
|
40863
|
+
return {
|
|
40864
|
+
written: await writeAll$1(files, io),
|
|
40865
|
+
verify: await verifyScaffold(files)
|
|
40866
|
+
};
|
|
40867
|
+
}
|
|
40868
|
+
//#endregion
|
|
40705
40869
|
//#region src/sources/introspect-supabase.ts
|
|
40706
40870
|
var mapOpenApiType = (prop) => {
|
|
40707
40871
|
if (Array.isArray(prop.enum)) return "enum";
|
|
@@ -40966,7 +41130,8 @@ function unwrapRows(body) {
|
|
|
40966
41130
|
return [];
|
|
40967
41131
|
}
|
|
40968
41132
|
/** Step 1-2: where does the data come from, and what tables does it have? */
|
|
40969
|
-
async function gatherData(flags, prompts, db, fetchText) {
|
|
41133
|
+
async function gatherData(flags, prompts, db, fetchText, io) {
|
|
41134
|
+
if (flags.from) return gatherFromSchemaFile(flags, flags.from, prompts, io);
|
|
40970
41135
|
let source = "sample";
|
|
40971
41136
|
if (flags.db) source = "db";
|
|
40972
41137
|
else if (flags.supabaseUrl) source = "supabase";
|
|
@@ -40998,6 +41163,41 @@ async function gatherData(flags, prompts, db, fetchText) {
|
|
|
40998
41163
|
title: dataset.name
|
|
40999
41164
|
};
|
|
41000
41165
|
}
|
|
41166
|
+
/**
|
|
41167
|
+
* A schema file names the tables and columns but holds no rows and no
|
|
41168
|
+
* connection, so the app runs on the in-memory source and the emitter seeds it
|
|
41169
|
+
* with sample rows. `--db` + `--url` is the path to a live database.
|
|
41170
|
+
*/
|
|
41171
|
+
async function gatherFromSchemaFile(flags, from, prompts, io) {
|
|
41172
|
+
const clash = flags.db ? "--db" : flags.supabaseUrl ? "--supabase-url" : flags.dataset ? "--dataset" : null;
|
|
41173
|
+
if (clash) throw new UserError(`--from and ${clash} both name the data source.`, "Pass one of them.");
|
|
41174
|
+
if (await io.readFile(from) == null) throw new UserError(`Schema file not found: ${from}`, "Pass the path to a Drizzle schema (.ts) or a Prisma schema (.prisma).");
|
|
41175
|
+
let all;
|
|
41176
|
+
try {
|
|
41177
|
+
all = await resolveSchemas(from, io);
|
|
41178
|
+
} catch (e) {
|
|
41179
|
+
throw new UserError(`Could not read ${from}: ${e instanceof Error ? e.message : String(e)}`, "Pass a Drizzle schema (.ts) or a Prisma schema (.prisma).");
|
|
41180
|
+
}
|
|
41181
|
+
if (all.length === 0) throw new UserError(`No tables or models found in ${from}.`, "Drizzle tables are read from pgTable / mysqlTable / sqliteTable calls, Prisma models from `model X {` blocks.");
|
|
41182
|
+
let picked = all.map((s) => s.name);
|
|
41183
|
+
if (!flags.yes) {
|
|
41184
|
+
prompts.say("");
|
|
41185
|
+
prompts.say(`Found ${all.length} ${all.length === 1 ? "table" : "tables"} in ${from}:`);
|
|
41186
|
+
all.forEach((s, i) => prompts.say(` ${i + 1}. ${s.name}`));
|
|
41187
|
+
picked = parseSelection(await prompts.ask("Which tables? (all, or a comma list of names/numbers)", "all"), picked);
|
|
41188
|
+
}
|
|
41189
|
+
const schemas = all.filter((s) => picked.includes(s.name));
|
|
41190
|
+
if (schemas.length === 0) throw new UserError("No tables picked - nothing to build.");
|
|
41191
|
+
const sources = {};
|
|
41192
|
+
for (const s of schemas) sources[s.name] = { kind: "memory" };
|
|
41193
|
+
prompts.say(` Read ${schemas.length} ${schemas.length === 1 ? "table" : "tables"} from ${from} (in-memory data with sample rows).`);
|
|
41194
|
+
return {
|
|
41195
|
+
schemas,
|
|
41196
|
+
sources,
|
|
41197
|
+
seed: {},
|
|
41198
|
+
kind: "memory"
|
|
41199
|
+
};
|
|
41200
|
+
}
|
|
41001
41201
|
async function gatherFromDatabase(flags, prompts, db) {
|
|
41002
41202
|
if (!db) throw new UserError("Connecting to a database is not available here.");
|
|
41003
41203
|
let dialect = flags.db;
|
|
@@ -41263,7 +41463,7 @@ async function confirmTargetFolder(outDir, flags, prompts, io, before) {
|
|
|
41263
41463
|
if (!isYes(await prompts.ask("Overwrite? (y/N)", "n"), false)) throw new UserError("Stopped without writing anything.", "Re-run with --out <dir> to build somewhere else.");
|
|
41264
41464
|
}
|
|
41265
41465
|
/** Merge-write the generated files, preserving edits outside managed regions. */
|
|
41266
|
-
async function writeAll
|
|
41466
|
+
async function writeAll(files, outDir, io) {
|
|
41267
41467
|
const written = [];
|
|
41268
41468
|
for (const file of files) {
|
|
41269
41469
|
const path = inDir(outDir, file.path);
|
|
@@ -41282,7 +41482,7 @@ async function runStudioInit(flags, prompts, db, io, fetchText = null) {
|
|
|
41282
41482
|
prompts.say("SvGrid Studio - let's build your app.");
|
|
41283
41483
|
const candidateDir = flags.out ?? ".";
|
|
41284
41484
|
const markersBefore = await markersIn(candidateDir, io);
|
|
41285
|
-
const data = await gatherData(flags, prompts, db, fetchText);
|
|
41485
|
+
const data = await gatherData(flags, prompts, db, fetchText, io);
|
|
41286
41486
|
const perEntity = await askScreens(data.schemas, flags, prompts);
|
|
41287
41487
|
const theme = await askTheme(flags, prompts);
|
|
41288
41488
|
const defaultTitle = flags.title ?? data.title ?? "My app";
|
|
@@ -41298,7 +41498,7 @@ async function runStudioInit(flags, prompts, db, io, fetchText = null) {
|
|
|
41298
41498
|
overviewDashboard: true,
|
|
41299
41499
|
...theme ? { theme } : {}
|
|
41300
41500
|
});
|
|
41301
|
-
const written = await writeAll
|
|
41501
|
+
const written = await writeAll(emitStudioAppBundle(project), outDir, io);
|
|
41302
41502
|
const configPath = inDir(outDir, "studio.config.json");
|
|
41303
41503
|
await io.writeFile(configPath, serializeProject(project));
|
|
41304
41504
|
written.push(configPath);
|
|
@@ -41466,57 +41666,6 @@ function buildStudioBugReport(input) {
|
|
|
41466
41666
|
};
|
|
41467
41667
|
}
|
|
41468
41668
|
//#endregion
|
|
41469
|
-
//#region src/studio/verify.ts
|
|
41470
|
-
async function verifyScaffold(files) {
|
|
41471
|
-
const issues = [];
|
|
41472
|
-
let checked = 0;
|
|
41473
|
-
let skipped = 0;
|
|
41474
|
-
let compile;
|
|
41475
|
-
try {
|
|
41476
|
-
compile = (await import("svelte/compiler")).compile;
|
|
41477
|
-
} catch {
|
|
41478
|
-
compile = void 0;
|
|
41479
|
-
}
|
|
41480
|
-
for (const file of files) {
|
|
41481
|
-
if (!file.path.endsWith(".svelte") || !compile) {
|
|
41482
|
-
skipped++;
|
|
41483
|
-
continue;
|
|
41484
|
-
}
|
|
41485
|
-
try {
|
|
41486
|
-
const { warnings } = compile(file.contents, {
|
|
41487
|
-
filename: file.path,
|
|
41488
|
-
generate: "client"
|
|
41489
|
-
});
|
|
41490
|
-
checked++;
|
|
41491
|
-
for (const w of warnings) issues.push({
|
|
41492
|
-
path: file.path,
|
|
41493
|
-
severity: "warning",
|
|
41494
|
-
message: w.message ?? "warning"
|
|
41495
|
-
});
|
|
41496
|
-
} catch (err) {
|
|
41497
|
-
checked++;
|
|
41498
|
-
issues.push({
|
|
41499
|
-
path: file.path,
|
|
41500
|
-
severity: "error",
|
|
41501
|
-
message: err instanceof Error ? err.message : String(err)
|
|
41502
|
-
});
|
|
41503
|
-
}
|
|
41504
|
-
}
|
|
41505
|
-
return {
|
|
41506
|
-
ok: !issues.some((i) => i.severity === "error"),
|
|
41507
|
-
issues,
|
|
41508
|
-
checked,
|
|
41509
|
-
skipped
|
|
41510
|
-
};
|
|
41511
|
-
}
|
|
41512
|
-
/** One-line human summary of a verify result, for tool output / logs. */
|
|
41513
|
-
function summarizeVerify(result) {
|
|
41514
|
-
if (result.checked === 0) return "verification skipped (Svelte compiler unavailable) - run svelte-check in your project.";
|
|
41515
|
-
const errors = result.issues.filter((i) => i.severity === "error");
|
|
41516
|
-
if (errors.length === 0) return `verified: ${result.checked} file(s) compile.`;
|
|
41517
|
-
return `VERIFICATION FAILED (${errors.length} error(s)):\n` + errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
|
|
41518
|
-
}
|
|
41519
|
-
//#endregion
|
|
41520
41669
|
//#region src/studio/copilot-core.ts
|
|
41521
41670
|
/**
|
|
41522
41671
|
* Studio Copilot - the pure, testable core. Builds the model request for "edit
|
|
@@ -41660,63 +41809,6 @@ function missingEnvKeys(envExample, envLocal) {
|
|
|
41660
41809
|
return declared.filter((k) => !present.has(k));
|
|
41661
41810
|
}
|
|
41662
41811
|
//#endregion
|
|
41663
|
-
//#region src/studio/cli.ts
|
|
41664
|
-
/** A `.prisma` file (by extension or a `model X {` block) parses as Prisma; otherwise Drizzle. */
|
|
41665
|
-
function isPrismaSource(path, source) {
|
|
41666
|
-
return /\.prisma$/i.test(path) || /\bmodel\s+\w+\s*\{/.test(source);
|
|
41667
|
-
}
|
|
41668
|
-
/** Resolve an EntitySchema from whichever input was provided. */
|
|
41669
|
-
async function resolveSchema(options, io) {
|
|
41670
|
-
if (options.schema) return options.schema;
|
|
41671
|
-
if (options.from) {
|
|
41672
|
-
const src = await io.readFile(options.from);
|
|
41673
|
-
if (src == null) throw new Error(`runStudioAdd: schema file not found: ${options.from}`);
|
|
41674
|
-
return isPrismaSource(options.from, src) ? introspectPrisma(src, options.table) : introspectDrizzle(src, options.table);
|
|
41675
|
-
}
|
|
41676
|
-
if (options.rows) return introspectJson(options.name ?? options.route ?? "entity", options.rows);
|
|
41677
|
-
throw new Error("runStudioAdd: provide `from` (a Drizzle or Prisma schema file), `rows`, or `schema`");
|
|
41678
|
-
}
|
|
41679
|
-
/**
|
|
41680
|
-
* Resolve **every** entity from a Drizzle or Prisma schema file (the `--all
|
|
41681
|
-
* --from` path), linked so foreign keys become working relation lookups.
|
|
41682
|
-
*/
|
|
41683
|
-
async function resolveSchemas(from, io) {
|
|
41684
|
-
const src = await io.readFile(from);
|
|
41685
|
-
if (src == null) throw new Error(`runStudioAddApp: schema file not found: ${from}`);
|
|
41686
|
-
return isPrismaSource(from, src) ? introspectPrismaAll(src) : introspectDrizzleAll(src);
|
|
41687
|
-
}
|
|
41688
|
-
/** Merge-write a set of generated files, preserving user edits outside managed regions. */
|
|
41689
|
-
async function writeAll(files, io) {
|
|
41690
|
-
const written = [];
|
|
41691
|
-
for (const file of files) {
|
|
41692
|
-
const existing = await io.readFile(file.path);
|
|
41693
|
-
if (skipUserOwned(file, existing != null)) continue;
|
|
41694
|
-
await io.writeFile(file.path, mergeManaged(existing, file.contents));
|
|
41695
|
-
written.push(file.path);
|
|
41696
|
-
}
|
|
41697
|
-
return written;
|
|
41698
|
-
}
|
|
41699
|
-
async function runStudioAdd(options, io) {
|
|
41700
|
-
const schema = await resolveSchema(options, io);
|
|
41701
|
-
const { files } = scaffold(schema, options);
|
|
41702
|
-
return {
|
|
41703
|
-
schema,
|
|
41704
|
-
written: await writeAll(files, io),
|
|
41705
|
-
verify: await verifyScaffold(files)
|
|
41706
|
-
};
|
|
41707
|
-
}
|
|
41708
|
-
/**
|
|
41709
|
-
* Scaffold a whole multi-entity app - every entity's screen plus a nav layout
|
|
41710
|
-
* and home page - and merge-write it. The `--all` path behind `svgrid-studio`.
|
|
41711
|
-
*/
|
|
41712
|
-
async function runStudioAddApp(schemas, options, io) {
|
|
41713
|
-
const { files } = scaffoldApp(schemas, options);
|
|
41714
|
-
return {
|
|
41715
|
-
written: await writeAll(files, io),
|
|
41716
|
-
verify: await verifyScaffold(files)
|
|
41717
|
-
};
|
|
41718
|
-
}
|
|
41719
|
-
//#endregion
|
|
41720
41812
|
//#region src/revoked.ts
|
|
41721
41813
|
var REVOKED_KEYS = /* @__PURE__ */ new Set([]);
|
|
41722
41814
|
/**
|
package/dist/sheet/objects.d.ts
CHANGED
|
@@ -92,6 +92,53 @@ export declare function objectAt(objects: ReadonlyArray<SheetObject>, point: {
|
|
|
92
92
|
width: number;
|
|
93
93
|
height: number;
|
|
94
94
|
} | null): SheetObject | null;
|
|
95
|
+
/**
|
|
96
|
+
* The four panes Freeze Panes splits a sheet into: the frozen corner, the
|
|
97
|
+
* frozen rows (which scroll across), the frozen columns (which scroll
|
|
98
|
+
* down), and the body (which scrolls both ways).
|
|
99
|
+
*/
|
|
100
|
+
export type SheetPane = 'corner' | 'rows' | 'cols' | 'body';
|
|
101
|
+
/**
|
|
102
|
+
* The pane an object belongs to: the one its anchor cell is in. Excel keeps
|
|
103
|
+
* an object with that pane, so one hung from a frozen cell is drawn over
|
|
104
|
+
* the frozen cells and stays put while the sheet scrolls under it.
|
|
105
|
+
*/
|
|
106
|
+
export declare function paneOf(anchor: {
|
|
107
|
+
row: number;
|
|
108
|
+
col: number;
|
|
109
|
+
}, freeze: {
|
|
110
|
+
rows: number;
|
|
111
|
+
cols: number;
|
|
112
|
+
}): SheetPane;
|
|
113
|
+
/**
|
|
114
|
+
* How much of an object its pane cuts off, in pixels from each edge of its
|
|
115
|
+
* box. `box` and `edges` are in the same frame: `left` measured from the
|
|
116
|
+
* inline start, `top` from the top.
|
|
117
|
+
*
|
|
118
|
+
* `edges.start` and `edges.top` are where the scrolling cells begin (past
|
|
119
|
+
* the frozen columns, or the row numbers when none are frozen; under the
|
|
120
|
+
* frozen rows, or the header), and `edges.end` and `edges.bottom` where the
|
|
121
|
+
* sheet's window ends. An object is cut on the sides its pane scrolls from,
|
|
122
|
+
* so it slides under the frozen cells, and always at the window's far
|
|
123
|
+
* edges. A frozen pane never scrolls past its start, so nothing is cut
|
|
124
|
+
* there and the object is drawn whole over its frozen cells.
|
|
125
|
+
*/
|
|
126
|
+
export declare function paneClip(pane: SheetPane, box: {
|
|
127
|
+
left: number;
|
|
128
|
+
top: number;
|
|
129
|
+
width: number;
|
|
130
|
+
height: number;
|
|
131
|
+
}, edges: {
|
|
132
|
+
start: number;
|
|
133
|
+
top: number;
|
|
134
|
+
end: number;
|
|
135
|
+
bottom: number;
|
|
136
|
+
}): {
|
|
137
|
+
start: number;
|
|
138
|
+
top: number;
|
|
139
|
+
end: number;
|
|
140
|
+
bottom: number;
|
|
141
|
+
};
|
|
95
142
|
/**
|
|
96
143
|
* The chart's spec, read from its range through `valueAt` (the computed
|
|
97
144
|
* value) and `textAt` (what the cell shows, for the labels). A chart of a
|
package/dist/sheet/objects.js
CHANGED
|
@@ -49,6 +49,39 @@ export function objectAt(objects, point, boxOf) {
|
|
|
49
49
|
}
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The pane an object belongs to: the one its anchor cell is in. Excel keeps
|
|
54
|
+
* an object with that pane, so one hung from a frozen cell is drawn over
|
|
55
|
+
* the frozen cells and stays put while the sheet scrolls under it.
|
|
56
|
+
*/
|
|
57
|
+
export function paneOf(anchor, freeze) {
|
|
58
|
+
const row = anchor.row < freeze.rows;
|
|
59
|
+
const col = anchor.col < freeze.cols;
|
|
60
|
+
return row && col ? 'corner' : row ? 'rows' : col ? 'cols' : 'body';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* How much of an object its pane cuts off, in pixels from each edge of its
|
|
64
|
+
* box. `box` and `edges` are in the same frame: `left` measured from the
|
|
65
|
+
* inline start, `top` from the top.
|
|
66
|
+
*
|
|
67
|
+
* `edges.start` and `edges.top` are where the scrolling cells begin (past
|
|
68
|
+
* the frozen columns, or the row numbers when none are frozen; under the
|
|
69
|
+
* frozen rows, or the header), and `edges.end` and `edges.bottom` where the
|
|
70
|
+
* sheet's window ends. An object is cut on the sides its pane scrolls from,
|
|
71
|
+
* so it slides under the frozen cells, and always at the window's far
|
|
72
|
+
* edges. A frozen pane never scrolls past its start, so nothing is cut
|
|
73
|
+
* there and the object is drawn whole over its frozen cells.
|
|
74
|
+
*/
|
|
75
|
+
export function paneClip(pane, box, edges) {
|
|
76
|
+
const across = pane === 'rows' || pane === 'body';
|
|
77
|
+
const down = pane === 'cols' || pane === 'body';
|
|
78
|
+
return {
|
|
79
|
+
start: across ? Math.max(0, edges.start - box.left) : 0,
|
|
80
|
+
top: down ? Math.max(0, edges.top - box.top) : 0,
|
|
81
|
+
end: Math.max(0, box.left + box.width - edges.end),
|
|
82
|
+
bottom: Math.max(0, box.top + box.height - edges.bottom),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
52
85
|
/** A cell's value as a number, or null when it is not one. */
|
|
53
86
|
function numberOf(value) {
|
|
54
87
|
if (typeof value === 'number')
|
package/dist/studio/cli.js
CHANGED
|
@@ -58,12 +58,66 @@ export async function runStudioAdd(options, io) {
|
|
|
58
58
|
const verify = await verifyScaffold(files);
|
|
59
59
|
return { schema, written, verify };
|
|
60
60
|
}
|
|
61
|
+
/** The stylesheets an `init`-generated layout imports, in cascade order. */
|
|
62
|
+
const APP_STYLESHEETS = ['app.css', 'custom.css'];
|
|
63
|
+
/**
|
|
64
|
+
* Fill the app-shell options the caller left out from the app already on disk,
|
|
65
|
+
* so `add --all` in a folder `init` generated keeps its stylesheets, title and
|
|
66
|
+
* theme. The shell layout from `init` carries no managed markers, so the merge
|
|
67
|
+
* write replaces it whole; anything it imported or named has to be carried
|
|
68
|
+
* over here or it is lost.
|
|
69
|
+
*
|
|
70
|
+
* Title: studio.config.json (shell brand, then title), else the `appTitle`
|
|
71
|
+
* constant of a layout an earlier `add --all` wrote.
|
|
72
|
+
*/
|
|
73
|
+
async function appShellDefaults(options, io) {
|
|
74
|
+
const stylesheets = options.stylesheets ?? [];
|
|
75
|
+
if (!options.stylesheets) {
|
|
76
|
+
for (const css of APP_STYLESHEETS) {
|
|
77
|
+
if ((await io.readFile(`src/${css}`)) != null)
|
|
78
|
+
stylesheets.push(css);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let title;
|
|
82
|
+
let theme;
|
|
83
|
+
const config = await io.readFile('studio.config.json');
|
|
84
|
+
if (config != null) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(config);
|
|
87
|
+
theme = parsed.theme;
|
|
88
|
+
const brand = typeof theme?.shell?.brand === 'string' ? theme.shell.brand.trim() : '';
|
|
89
|
+
title = brand || (typeof parsed.title === 'string' ? parsed.title : undefined);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* unreadable config: fall back to the layout below */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!title) {
|
|
96
|
+
const layout = await io.readFile('src/routes/+layout.svelte');
|
|
97
|
+
const m = layout?.match(/const appTitle = ("(?:[^"\\]|\\.)*")/);
|
|
98
|
+
if (m) {
|
|
99
|
+
try {
|
|
100
|
+
title = JSON.parse(m[1]);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* not a JSON string literal: keep the default */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
...options,
|
|
109
|
+
stylesheets,
|
|
110
|
+
...(options.appTitle == null && title ? { appTitle: title } : {}),
|
|
111
|
+
...(options.theme == null && typeof theme?.preset === 'string' ? { theme: theme.preset } : {}),
|
|
112
|
+
...(options.dark == null && theme?.mode === 'dark' ? { dark: true } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
61
115
|
/**
|
|
62
116
|
* Scaffold a whole multi-entity app - every entity's screen plus a nav layout
|
|
63
117
|
* and home page - and merge-write it. The `--all` path behind `svgrid-studio`.
|
|
64
118
|
*/
|
|
65
119
|
export async function runStudioAddApp(schemas, options, io) {
|
|
66
|
-
const { files } = scaffoldApp(schemas, options);
|
|
120
|
+
const { files } = scaffoldApp(schemas, await appShellDefaults(options, io));
|
|
67
121
|
const written = await writeAll(files, io);
|
|
68
122
|
const verify = await verifyScaffold(files);
|
|
69
123
|
return { written, verify };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type StudioIO } from './cli.js';
|
|
2
2
|
import { type DbExecute, type SqlDialectName } from './introspect-db.js';
|
|
3
3
|
import { type StudioProject } from './project.js';
|
|
4
4
|
/** Terminal I/O, injected so the flow can be scripted in tests. */
|
|
@@ -30,6 +30,11 @@ export type InitFlags = {
|
|
|
30
30
|
supabaseUrl?: string;
|
|
31
31
|
/** Supabase anon key. */
|
|
32
32
|
supabaseKey?: string;
|
|
33
|
+
/**
|
|
34
|
+
* A Drizzle (.ts) or Prisma (.prisma) schema file. Skips the source question
|
|
35
|
+
* and builds the app from its tables, served from memory with sample rows.
|
|
36
|
+
*/
|
|
37
|
+
from?: string;
|
|
33
38
|
/** Pick a starter dataset by id, skipping the source question. */
|
|
34
39
|
dataset?: string;
|
|
35
40
|
/** Theme preset id. */
|