@alstar/studio 0.0.0-beta.14 → 0.0.0-beta.16

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.
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path'
3
+ import { type SSGPlugin, toSSG } from 'hono/ssg'
4
+ import fs from 'fs/promises'
5
+
6
+ const root = path.resolve('.')
7
+
8
+ let app
9
+
10
+ try {
11
+ app = (await import(path.join(root, 'index.ts'))).default
12
+ } catch (error) {
13
+ console.log(error)
14
+ console.log('Found no app')
15
+ console.log('Main app needs to be the default export in ./index.ts')
16
+ process.exit(1)
17
+ }
18
+
19
+ await existingStaticFolder()
20
+
21
+ const excludeStudioPlugin: SSGPlugin = {
22
+ beforeRequestHook: (req) => {
23
+ if (!req.url.includes('/studio/')) {
24
+ return req
25
+ }
26
+ return false
27
+ },
28
+ }
29
+
30
+ await toSSG(app, fs, { plugins: [excludeStudioPlugin] })
31
+
32
+ await fs.cp(path.join(root, 'public'), path.join(root, 'static'), {
33
+ recursive: true,
34
+ })
35
+
36
+ console.log('🚀 Done generating static build')
37
+
38
+ process.exit(0)
39
+
40
+ async function existingStaticFolder() {
41
+ return fs.rm(path.join(root, 'static'), { recursive: true }).catch(() => {})
42
+ }
@@ -26,7 +26,7 @@ export default (props: {
26
26
 
27
27
  if (!data) return html`<p>No block</p>`
28
28
 
29
- const entries = Object.entries(structure.children)
29
+ const entries = Object.entries(structure.blocks)
30
30
 
31
31
  const rows = query.blocks({ parent_id: data.id })
32
32
 
@@ -1,6 +1,7 @@
1
1
  import { Field } from './fields/index.ts'
2
2
  import type { BlocksFieldDefStructure, FieldDefStructure } from '../types.ts'
3
3
  import BlockFieldRenderer from './BlockFieldRenderer.ts'
4
+ import { BlockFieldInstance } from '../utils/define.ts'
4
5
 
5
6
  export default (props: {
6
7
  entryId: number
@@ -11,6 +12,10 @@ export default (props: {
11
12
  }) => {
12
13
  const { entryId, parentId, structure, name, id } = props
13
14
 
15
+ if (structure.instanceOf === BlockFieldInstance) {
16
+ return BlockFieldRenderer({ entryId, parentId, name, structure, id })
17
+ }
18
+
14
19
  switch (structure.type) {
15
20
  case 'text': {
16
21
  return Field.Text({ entryId, parentId, name, id, structure })
@@ -28,8 +33,8 @@ export default (props: {
28
33
  return Field.Text({ entryId, parentId, name, structure, id })
29
34
  }
30
35
 
31
- case 'blocks': {
32
- return BlockFieldRenderer({ entryId, parentId, name, structure, id })
36
+ case 'reference': {
37
+ return Field.Reference({ entryId, parentId, name, structure, id })
33
38
  }
34
39
  }
35
40
  }
@@ -41,6 +41,11 @@ export default (props: {
41
41
  }
42
42
  }
43
43
  } catch (error) {
44
- return html`<p>Error rendering "${name}"</p>`
44
+ console.log(error)
45
+
46
+ return html`
47
+ <p>Error rendering "${name}"</p>
48
+ <pre><code>${JSON.stringify(error, null, 2)}</code></pre>
49
+ `
45
50
  }
46
51
  }
@@ -0,0 +1,67 @@
1
+ import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
2
+ import { html } from '../../utils/html.ts'
3
+ import type { FieldDefStructure, ReferenceFieldStructure } from '../../types.ts'
4
+ import { query } from '../../queries/index.ts'
5
+
6
+ export default (props: {
7
+ entryId: number
8
+ parentId: number
9
+ name: string
10
+ id?: number
11
+ structure: ReferenceFieldStructure
12
+ sortOrder?: number
13
+ }) => {
14
+ const { entryId, parentId, name, structure, sortOrder = 0, id } = props
15
+
16
+ const data = getOrCreateRow({
17
+ parentId,
18
+ name,
19
+ field: structure,
20
+ sortOrder,
21
+ id,
22
+ })
23
+
24
+ if (!data) return html`<p>No block</p>`
25
+
26
+ const entries = query.roots({ type: structure.to }, { depth: 1 })
27
+
28
+ return html`
29
+ <form
30
+ data-on-input="@patch('/studio/api/block', {
31
+ contentType: 'form',
32
+ headers: {
33
+ render: ''
34
+ }
35
+ })"
36
+ >
37
+ <hgroup>
38
+ <label for="block-${data.id}">${structure.label}</label>
39
+ ${structure.description &&
40
+ html`
41
+ <p><small>${structure.description}</small></p>
42
+ `}
43
+ </hgroup>
44
+
45
+ <input
46
+ list="entries-${data.id}"
47
+ id="block-${data.id}"
48
+ name="value"
49
+ value="${data.value}"
50
+ />
51
+ <datalist id="entries-${data.id}">
52
+ ${entries.map((entry) => {
53
+ return html`<option value="${entry.fields.slug.value}">
54
+ ${entry.fields.title.value}
55
+ </option>`
56
+ })}
57
+ </datalist>
58
+
59
+ <input type="hidden" name="id" value="${data.id}" />
60
+
61
+ <!-- <input type="hidden" name="type" value="{structure.type}" />
62
+ <input type="hidden" name="entryId" value="{entryId}" />
63
+ <input type="hidden" name="parentId" value="{parentId}" />
64
+ <input type="hidden" name="name" value="{name}" /> -->
65
+ </form>
66
+ `
67
+ }
@@ -1,7 +1,8 @@
1
1
  import Text from './Text.ts'
2
2
  import Markdown from './Markdown.ts'
3
3
  import Slug, { routes as slugRoutes } from './Slug.ts'
4
+ import Reference from './Reference.ts'
4
5
 
5
- export const Field = { Text, Markdown, Slug }
6
+ export const Field = { Text, Markdown, Slug, Reference }
6
7
 
7
8
  export const fieldRoutes = [slugRoutes]
package/index.ts CHANGED
@@ -14,6 +14,7 @@ import { getConfig } from './utils/get-config.ts'
14
14
  import startupLog from './utils/startup-log.ts'
15
15
  import { apiRoutes } from './api/index.ts'
16
16
  import { mcpRoutes } from './api/mcp.ts'
17
+ import packageJSON from './package.json' with { type: 'json' }
17
18
 
18
19
  import auth from './utils/auth.ts'
19
20
  import ErrorPage from './pages/error.ts'
@@ -25,6 +26,7 @@ export let rootdir = './node_modules/@alstar/studio'
25
26
  export let studioStructure: types.Structure = {}
26
27
  export let studioConfig: types.StudioConfig = {
27
28
  siteName: '',
29
+ fileBasedRouter: true,
28
30
  port: 3000,
29
31
  structure: {},
30
32
  }
@@ -72,8 +74,10 @@ const createStudio = async (config: types.StudioConfig) => {
72
74
  /**
73
75
  * User pages
74
76
  */
75
- const pages = await fileBasedRouter('./pages')
76
- if (pages) app.route('/', pages)
77
+ if (studioConfig.fileBasedRouter) {
78
+ const pages = await fileBasedRouter('./pages')
79
+ if (pages) app.route('/', pages)
80
+ }
77
81
 
78
82
  /**
79
83
  * Error pages
@@ -95,7 +99,8 @@ const createStudio = async (config: types.StudioConfig) => {
95
99
  '/studio/backups/*',
96
100
  serveStatic({
97
101
  root: './',
98
- rewriteRequestPath: (path) => path.replace(/^\/studio\/backups/, '/backups'),
102
+ rewriteRequestPath: (path) =>
103
+ path.replace(/^\/studio\/backups/, '/backups'),
99
104
  }),
100
105
  )
101
106
 
@@ -141,3 +146,4 @@ export { type RequestContext } from './types.ts'
141
146
  export { createStudio }
142
147
  export { html, type HtmlEscapedString } from './utils/html.ts'
143
148
  export { query } from './queries/index.ts'
149
+ export const version = packageJSON.version
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@alstar/studio",
3
- "version": "0.0.0-beta.14",
3
+ "version": "0.0.0-beta.16",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
+ "bin": {
7
+ "alstar": "./bin/build-ssg.ts"
8
+ },
6
9
  "engines": {
7
10
  "node": ">=23.8"
8
11
  },
@@ -10,9 +13,9 @@
10
13
  "@hono/node-server": "^1.18.1",
11
14
  "@starfederation/datastar-sdk": "1.0.0-RC.1",
12
15
  "hono": "^4.8.12",
13
- "@alstar/refresher": "0.0.0-beta.3",
14
16
  "@alstar/db": "0.0.0-beta.1",
15
- "@alstar/ui": "0.0.0-beta.1"
17
+ "@alstar/refresher": "0.0.0-beta.3",
18
+ "@alstar/ui": "0.0.0-beta.3"
16
19
  },
17
20
  "devDependencies": {
18
21
  "@types/node": "^24.1.0",
@@ -1,4 +1,5 @@
1
1
  @import 'https://esm.sh/@alstar/ui/red.css';
2
+ /* @import '../node_modules/@alstar/ui/slate.css'; */
2
3
  @import './css/admin-panel.css';
3
4
  @import './css/blocks-field.css';
4
5
  @import './css/settings.css';
@@ -7,7 +7,7 @@ function buildForest(blocks: DBBlockResult[]): DBBlockResult[] {
7
7
  const roots: DBBlockResult[] = []
8
8
 
9
9
  for (const block of blocks) {
10
- block.children = []
10
+ block.blocks = []
11
11
  map.set(block.id, block)
12
12
  }
13
13
 
@@ -16,14 +16,14 @@ function buildForest(blocks: DBBlockResult[]): DBBlockResult[] {
16
16
  roots.push(block)
17
17
  } else {
18
18
  const parent = map.get(block.parent_id)
19
- if (parent) parent.children!.push(block)
19
+ if (parent) parent.blocks!.push(block)
20
20
  }
21
21
  }
22
22
 
23
- // Sort children by sort_order recursively
23
+ // Sort blocks by sort_order recursively
24
24
  const sortChildren = (node: DBBlockResult) => {
25
- node.children!.sort((a, b) => a.sort_order - b.sort_order)
26
- node.children!.forEach(sortChildren)
25
+ node.blocks!.sort((a, b) => a.sort_order - b.sort_order)
26
+ node.blocks!.forEach(sortChildren)
27
27
  }
28
28
  roots.forEach(sortChildren)
29
29
 
@@ -35,7 +35,7 @@ function buildTree(blocks: DBBlockResult[]): DBBlockResult {
35
35
  const roots: DBBlockResult[] = []
36
36
 
37
37
  for (const block of blocks) {
38
- block.children = []
38
+ block.blocks = []
39
39
  map.set(block.id, block)
40
40
  }
41
41
 
@@ -44,14 +44,14 @@ function buildTree(blocks: DBBlockResult[]): DBBlockResult {
44
44
  roots.push(block)
45
45
  } else {
46
46
  const parent = map.get(block.parent_id)
47
- if (parent) parent.children!.push(block)
47
+ if (parent) parent.blocks!.push(block)
48
48
  }
49
49
  }
50
50
 
51
- // Sort children by sort_order recursively
51
+ // Sort blocks by sort_order recursively
52
52
  const sortChildren = (node: DBBlockResult) => {
53
- node.children!.sort((a, b) => a.sort_order - b.sort_order)
54
- node.children!.forEach(sortChildren)
53
+ node.blocks!.sort((a, b) => a.sort_order - b.sort_order)
54
+ node.blocks!.forEach(sortChildren)
55
55
  }
56
56
  roots.forEach(sortChildren)
57
57
 
@@ -65,7 +65,7 @@ function transformBlocksTree(
65
65
  const fields: Record<string, DBBlockResult> = {}
66
66
  let hasFields = false
67
67
 
68
- for (const child of block.children ?? []) {
68
+ for (const child of block.blocks ?? []) {
69
69
  const transformedChild = transformBlocksTree(
70
70
  child,
71
71
  child.type === 'blocks',
@@ -82,7 +82,7 @@ function transformBlocksTree(
82
82
  }
83
83
 
84
84
  if (!isBlocksChild) {
85
- delete block.children
85
+ delete block.blocks
86
86
  } else {
87
87
  delete block.fields
88
88
  }
@@ -265,7 +265,7 @@ function buildTree2(items: DBRow[]): TODO | null {
265
265
 
266
266
  let root: TODO | null = null;
267
267
 
268
- // Second pass: assign children to parents
268
+ // Second pass: assign blocks to parents
269
269
  for (const item of map.values()) {
270
270
  if (item.parent_id === null) {
271
271
  root = item; // Root node
@@ -273,8 +273,8 @@ function buildTree2(items: DBRow[]): TODO | null {
273
273
  const parent = map.get(item.parent_id);
274
274
  if (parent) {
275
275
  if(parent.type === 'blocks') {
276
- if (!parent.children) parent.children = [];
277
- parent.children.push(item);
276
+ if (!parent.blocks) parent.blocks = [];
277
+ parent.blocks.push(item);
278
278
  } else {
279
279
  if (!parent.fields) parent.fields = {};
280
280
  parent.fields[item.name] = item;
package/schemas.ts CHANGED
@@ -16,7 +16,7 @@ export const blocksTable = {
16
16
  columns: sql`
17
17
  name TEXT not null,
18
18
  label TEXT not null,
19
- type TEXT not null,
19
+ type TEXT,
20
20
  value TEXT,
21
21
  options JSON,
22
22
  status TEXT default 'enabled',
package/types.ts CHANGED
@@ -21,14 +21,13 @@ export type DeepReadonly<T> =
21
21
  export type PrimitiveField = {
22
22
  name: string
23
23
  label: string
24
- type: 'text' | 'slug' | 'markdown' | 'image'
24
+ type: 'text' | 'slug' | 'markdown' | 'image' | 'reference'
25
25
  }
26
26
 
27
27
  export type BlockField = {
28
28
  name: string
29
29
  label: string
30
- type: 'blocks'
31
- children: Record<string, Field | Block>
30
+ blocks: Record<string, Field | Block>
32
31
  }
33
32
 
34
33
  export type Field = PrimitiveField | BlockField
@@ -44,7 +43,7 @@ export type Structure = Record<string, BlockDefStructure>
44
43
  // export type Structure = Record<string, BlockDefStructure>
45
44
 
46
45
  // --- Field & block definitions ---
47
- type FieldType = 'text' | 'slug' | 'markdown' | 'image'
46
+ type FieldType = 'text' | 'slug' | 'markdown' | 'image' | 'reference'
48
47
 
49
48
  interface BaseField {
50
49
  label: string
@@ -68,19 +67,27 @@ interface ImageFieldStructure extends ImageField {
68
67
  instanceOf: typeof FieldInstance
69
68
  }
70
69
 
70
+ interface ReferenceField extends BaseField {
71
+ type: 'reference'
72
+ to: string | string[]
73
+ }
74
+
75
+ export interface ReferenceFieldStructure extends ReferenceField {
76
+ instanceOf: typeof FieldInstance
77
+ }
78
+
71
79
  export interface BlocksFieldDef {
72
80
  label: string
73
- type: 'blocks'
74
81
  description?: string
75
- children: Record<string, BlockDefStructure | FieldDefStructure>
82
+ blocks: Record<string, BlockDefStructure | FieldDefStructure>
76
83
  }
77
84
 
78
85
  export interface BlocksFieldDefStructure extends BlocksFieldDef {
79
86
  instanceOf: typeof BlockFieldInstance
80
87
  }
81
88
 
82
- export type FieldDef = TextField | ImageField
83
- export type FieldDefStructure = TextFieldStructure | ImageFieldStructure
89
+ export type FieldDef = TextField | ImageField | ReferenceField
90
+ export type FieldDefStructure = TextFieldStructure | ImageFieldStructure | ReferenceFieldStructure
84
91
 
85
92
  export interface BlockDef {
86
93
  label: string
@@ -127,8 +134,7 @@ export type DBPrimitiveFieldResult = BaseDBResult & {
127
134
  }
128
135
 
129
136
  export type DBBlockFieldResult = BaseDBResult & {
130
- type: 'blocks'
131
- children: DBBlockResult[]
137
+ blocks: DBBlockResult[]
132
138
  }
133
139
 
134
140
  export type DBBlockResult = BaseDBResult & {
@@ -153,6 +159,7 @@ export type BlockStatus = 'enabled' | 'disabled'
153
159
  export type StudioConfig = {
154
160
  siteName?: string
155
161
  honoConfig?: HonoOptions<BlankEnv>
162
+ fileBasedRouter?: boolean,
156
163
  port?: number
157
164
  structure: Structure
158
165
  }
@@ -5,6 +5,7 @@ import type {
5
5
  BlocksFieldDefStructure,
6
6
  FieldDefStructure,
7
7
  } from '../types.ts'
8
+ import { BlockFieldInstance } from './define.ts'
8
9
 
9
10
  export function getOrCreateRow(props: {
10
11
  parentId: string | number | null
@@ -32,7 +33,7 @@ export function getOrCreateRow(props: {
32
33
  const change = db.insertInto('blocks', {
33
34
  name: name?.toString(),
34
35
  label: field.label?.toString(),
35
- type: field.type?.toString(),
36
+ type: field.instanceOf === BlockFieldInstance ? 'blocks' : field.type?.toString(),
36
37
  sort_order: sortOrder,
37
38
  parent_id: parentId,
38
39
  })