@ossy/app 1.15.0 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import url from 'node:url'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { createRequire } from 'node:module'
6
+ import arg from 'arg'
7
+ import { rollup } from 'rollup'
8
+ import babel from '@rollup/plugin-babel'
9
+ import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
10
+ import resolveCommonJsDependencies from '@rollup/plugin-commonjs'
11
+ import json from '@rollup/plugin-json'
12
+ import replace from '@rollup/plugin-replace'
13
+ import nodeExternals from 'rollup-plugin-node-externals'
14
+ import getPlatformFiles from './getPlatformFiles.task.js'
15
+
16
+ function resetBuildDir (buildPath) {
17
+ fs.rmSync(buildPath, { recursive: true, force: true })
18
+ fs.mkdirSync(buildPath, { recursive: true })
19
+ }
20
+
21
+ export async function build (cliArgs = []) {
22
+
23
+ const options = arg({
24
+ '--config': String,
25
+ '-c': '--config',
26
+ }, { argv: cliArgs })
27
+
28
+ const cwd = process.cwd()
29
+ const buildPath = path.resolve(cwd, 'build')
30
+ const srcDir = path.resolve(cwd, 'src')
31
+ const publicDir = path.resolve(cwd, 'public')
32
+
33
+ resetBuildDir(buildPath)
34
+ const entries = await getPlatformFiles(srcDir)
35
+
36
+ if (publicDir && fs.existsSync(publicDir)) {
37
+ fs.cpSync(publicDir, path.join(buildPath, 'public'), { recursive: true, force: true })
38
+ }
39
+
40
+ const bundle = await rollup({
41
+ input: entries,
42
+ plugins: [
43
+ replace({
44
+ preventAssignment: true,
45
+ 'process.env.NODE_ENV': '"production"',
46
+ }),
47
+ json(),
48
+ nodeExternals({
49
+ deps: false,
50
+ devDeps: true,
51
+ peerDeps: false,
52
+ packagePath: path.join(process.cwd(), 'package.json'),
53
+ }),
54
+ resolveCommonJsDependencies(),
55
+ resolveDependencies({ preferBuiltins: true }),
56
+ babel({
57
+ babelHelpers: 'bundled',
58
+ extensions: ['.jsx', '.tsx'],
59
+ presets: [['@babel/preset-react', { runtime: 'automatic' }]],
60
+ }),
61
+ ],
62
+ })
63
+
64
+ await bundle.write({
65
+ dir: buildPath,
66
+ format: 'esm',
67
+ // entryFileNames: (chunk) => (chunk.name === 'server' ? 'ssr/app.mjs' : 'public/static/app.js'),
68
+ chunkFileNames: 'public/static/chunks/[name]-[hash].js',
69
+ plugins: [],
70
+ })
71
+
72
+ await bundle.close()
73
+
74
+ }
@@ -0,0 +1,234 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import url from 'node:url'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { createRequire } from 'node:module'
6
+ import arg from 'arg'
7
+ import { rollup } from 'rollup'
8
+ import babel from '@rollup/plugin-babel'
9
+ import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
10
+ import resolveCommonJsDependencies from '@rollup/plugin-commonjs'
11
+ import json from '@rollup/plugin-json'
12
+ import replace from '@rollup/plugin-replace'
13
+ import nodeExternals from 'rollup-plugin-node-externals'
14
+
15
+ export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
16
+ export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
17
+ export const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
18
+ const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.(mjs|cjs|js)$/
19
+
20
+ export const OSSY_GEN_DIRNAME = '.ossy'
21
+ export const OSSY_GEN_ENTRIES_BASENAME = 'entries.generated.json'
22
+ export const OSSY_GEN_PAGES_BASENAME = 'pages.generated.json'
23
+ export const OSSY_GEN_API_BASENAME = 'api.generated.json'
24
+ export const OSSY_GEN_TASKS_BASENAME = 'tasks.generated.json'
25
+ export const OSSY_RESOURCE_TEMPLATES_OUT = 'resource-templates.generated.json'
26
+
27
+ function resetBuildDir (buildPath) {
28
+ fs.rmSync(buildPath, { recursive: true, force: true })
29
+ const dir = path.join(buildPath, OSSY_GEN_DIRNAME)
30
+ fs.mkdirSync(dir, { recursive: true })
31
+ return dir
32
+ }
33
+
34
+ function relToGeneratedImport (generatedAbs, targetAbs) {
35
+ return path.relative(path.dirname(generatedAbs), targetAbs).replace(/\\/g, '/')
36
+ }
37
+
38
+ export function discoverFilesByPattern (srcDir, filePattern) {
39
+ const dir = path.resolve(srcDir)
40
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
41
+ return []
42
+ }
43
+ const files = []
44
+ const walk = (d) => {
45
+ const entries = fs.readdirSync(d, { withFileTypes: true })
46
+ for (const e of entries) {
47
+ const full = path.join(d, e.name)
48
+ if (e.isDirectory()) walk(full)
49
+ else if (filePattern.test(e.name)) files.push(full)
50
+ }
51
+ }
52
+ walk(dir)
53
+ return files
54
+ }
55
+
56
+
57
+ function writeJson (filePath, data) {
58
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
59
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
60
+ }
61
+
62
+ function filePathToRoute (filePath, srcDir) {
63
+ const rel = path.relative(srcDir, filePath).replace(/\\/g, '/')
64
+ let pathPart = rel.replace(PAGE_FILE_PATTERN, '').replace(/\/index$/, '').replace(/\/home$/, '') || 'home'
65
+ if (pathPart === 'index' || pathPart === 'home') pathPart = 'home'
66
+ const id = pathPart === 'home' ? 'home' : pathPart.replace(/\//g, '-')
67
+ const routePath = pathPart === 'home' ? '/' : '/' + pathPart
68
+ return { id, path: routePath }
69
+ }
70
+
71
+ function pageRouteFromSource (pageAbsPath, srcDir) {
72
+ const derived = filePathToRoute(pageAbsPath, srcDir)
73
+ try {
74
+ const src = fs.readFileSync(pageAbsPath, 'utf8')
75
+ const metaIdx = src.indexOf('export const metadata')
76
+ if (metaIdx === -1) return derived
77
+ const after = src.slice(metaIdx)
78
+ const id = after.match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1] ?? derived.id
79
+ const strPath = after.match(/\bpath\s*:\s*['"]([^'"]+)['"]/)?.[1]
80
+ if (strPath) return { id, path: strPath }
81
+ const pathObjBody = after.match(/\bpath\s*:\s*\{([\s\S]*?)\}/)?.[1]
82
+ if (pathObjBody) {
83
+ const languagePathEntries = [...pathObjBody.matchAll(/([A-Za-z0-9_]+)\s*:\s*['"]([^'"]+)['"]/g)]
84
+ if (languagePathEntries.length > 0) {
85
+ return {
86
+ id,
87
+ path: Object.fromEntries(languagePathEntries.map(([, language, routePath]) => [language, routePath])),
88
+ }
89
+ }
90
+ }
91
+ return { id, path: derived.path }
92
+ } catch {
93
+ return derived
94
+ }
95
+ }
96
+
97
+ function generateHydrateEntry ({ pageFiles, srcDir, stubAbsPath }) {
98
+ const seenIds = new Set()
99
+ const pageLines = pageFiles.map((f) => {
100
+ const { id } = pageRouteFromSource(f, srcDir)
101
+ if (seenIds.has(id)) {
102
+ throw new Error(`[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`)
103
+ }
104
+ seenIds.add(id)
105
+ const rel = relToGeneratedImport(stubAbsPath, f)
106
+ return ` ${JSON.stringify(id)}: () => import('./${rel}'),`
107
+ })
108
+ return [
109
+ '// Generated by @ossy/app — do not edit',
110
+ "import { createElement } from 'react'",
111
+ "import { hydrateRoot } from 'react-dom/client'",
112
+ "import { App } from '@ossy/connected-components'",
113
+ 'const config = window.__INITIAL_APP_CONFIG__ || {}',
114
+ 'const pages = {',
115
+ ...pageLines,
116
+ '}',
117
+ 'const load = pages[config.pageId]',
118
+ 'if (load) {',
119
+ ' load().then((mod) => {',
120
+ ' const Page = mod.default',
121
+ ' const metadata = mod.metadata || {}',
122
+ ' function PageShell (props) {',
123
+ " return createElement('html', { lang: props.defaultLanguage || 'en' },",
124
+ " createElement('head', null,",
125
+ " createElement('meta', { charSet: 'utf-8' }),",
126
+ " createElement('title', null, metadata.title || ''),",
127
+ ' ),',
128
+ ' createElement(App, props,',
129
+ ' createElement(Page, props)',
130
+ ' )',
131
+ ' )',
132
+ ' }',
133
+ ' hydrateRoot(document, createElement(PageShell, config))',
134
+ ' })',
135
+ '}',
136
+ '',
137
+ ].join('\n')
138
+ }
139
+
140
+ function generateSsrEntry ({ pageFiles, srcDir, stubAbsPath }) {
141
+ const seenIds = new Set()
142
+ const pagesLiteral = []
143
+ const pageModuleLines = []
144
+ for (const f of pageFiles) {
145
+ const { id, path: routePath } = pageRouteFromSource(f, srcDir)
146
+ if (seenIds.has(id)) {
147
+ throw new Error(`[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`)
148
+ }
149
+ seenIds.add(id)
150
+ const rel = relToGeneratedImport(stubAbsPath, f)
151
+ pagesLiteral.push(` { id: ${JSON.stringify(id)}, path: ${JSON.stringify(routePath)} },`)
152
+ pageModuleLines.push(` ${JSON.stringify(id)}: () => import('./${rel}'),`)
153
+ }
154
+ return [
155
+ '// Generated by @ossy/app — do not edit',
156
+ "import { createElement } from 'react'",
157
+ "import { renderToPipeableStream } from 'react-dom/server'",
158
+ "import { Writable } from 'node:stream'",
159
+ "import { App } from '@ossy/connected-components'",
160
+ 'export const pages = [',
161
+ ...pagesLiteral,
162
+ ']',
163
+ 'const pageModules = {',
164
+ ...pageModuleLines,
165
+ '}',
166
+ 'function PageShell (props) {',
167
+ ' const meta = props._pageMeta || {}',
168
+ " return createElement('html', { lang: props.defaultLanguage || 'en' },",
169
+ " createElement('head', null,",
170
+ " createElement('meta', { charSet: 'utf-8' }),",
171
+ " createElement('title', null, meta.title || ''),",
172
+ ' ),',
173
+ ' createElement(App, props,',
174
+ ' createElement(props._pageComponent, props)',
175
+ ' )',
176
+ ' )',
177
+ '}',
178
+ 'export async function renderPage (pageId, props) {',
179
+ ' const load = pageModules[pageId]',
180
+ ' if (!load) throw new Error(`[@ossy/app] Unknown page id: ${pageId}`)',
181
+ ' const mod = await load()',
182
+ ' const merged = { ...props, _pageComponent: mod.default, _pageMeta: mod.metadata || {} }',
183
+ ' return new Promise((resolve, reject) => {',
184
+ " let html = ''",
185
+ ' const writable = new Writable({',
186
+ ' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
187
+ ' })',
188
+ ' const { pipe } = renderToPipeableStream(createElement(PageShell, merged), {',
189
+ ' onAllReady () { pipe(writable) },',
190
+ ' onError (err) { reject(err) },',
191
+ ' })',
192
+ " writable.on('finish', () => resolve(html))",
193
+ ' })',
194
+ '}',
195
+ '',
196
+ ].join('\n')
197
+ }
198
+
199
+ export async function build (cliArgs = []) {
200
+
201
+ const options = arg({
202
+ '--config': String,
203
+ '-c': '--config',
204
+ }, { argv: cliArgs })
205
+
206
+ const cwd = process.cwd()
207
+ const buildPath = path.resolve(cwd, 'build')
208
+ const ossyDir = path.join(buildPath, OSSY_GEN_DIRNAME)
209
+ const srcDir = path.resolve(cwd, 'src')
210
+ const publicDir = path.resolve(cwd, 'public')
211
+ const configPath = path.resolve(cwd, options['--config'] || 'src/config.js')
212
+ const middlewarePath = fs.existsSync(path.resolve(cwd, 'src/middleware.js')) ? path.resolve(cwd, 'src/middleware.js') : null
213
+
214
+ resetBuildDir(buildPath)
215
+
216
+ const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
217
+ const apiFiles = discoverFilesByPattern(srcDir, API_FILE_PATTERN)
218
+ const taskFiles = discoverFilesByPattern(srcDir, TASK_FILE_PATTERN)
219
+ const resourceTemplatesFiles = discoverFilesByPattern(srcDir, TASK_FILE_PATTERN)
220
+
221
+ const entries = [
222
+ ...(middlewarePath ? [middlewarePath] : []),
223
+ ...(configPath ? [configPath] : []),
224
+ ...pageFiles,
225
+ ...apiFiles,
226
+ ...taskFiles,
227
+ ...resourceTemplatesFiles,
228
+ ]
229
+
230
+ fs.mkdirSync(path.dirname(path.join(ossyDir, OSSY_GEN_ENTRIES_BASENAME)), { recursive: true })
231
+ fs.writeFileSync(path.join(ossyDir, OSSY_GEN_ENTRIES_BASENAME), `${JSON.stringify(entries, null, 2)}\n`, 'utf8')
232
+
233
+
234
+ }
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import url from 'node:url'
4
+
5
+ export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
6
+ export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
7
+ export const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
8
+ export const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.(mjs|cjs|js)$/
9
+
10
+ function discoverFilesByPattern (srcDir, filePattern) {
11
+ const dir = path.resolve(srcDir)
12
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
13
+ return []
14
+ }
15
+ const files = []
16
+ const walk = (d) => {
17
+ const entries = fs.readdirSync(d, { withFileTypes: true })
18
+ for (const e of entries) {
19
+ const full = path.join(d, e.name)
20
+ if (e.isDirectory()) walk(full)
21
+ else if (filePattern.test(e.name)) files.push(full)
22
+ }
23
+ }
24
+ walk(dir)
25
+ return files
26
+ }
27
+
28
+
29
+ export default async function getPlatformFiles(_folder) {
30
+ const cwd = process.cwd()
31
+ const folder = path.resolve(cwd, _folder)
32
+
33
+ const configPath = fs.existsSync(path.resolve(folder, 'config.js')) ? path.resolve(folder, 'config.js') : null
34
+ const middlewarePath = fs.existsSync(path.resolve(folder, 'middleware.js')) ? path.resolve(folder, 'middleware.js') : null
35
+
36
+ const pageFiles = discoverFilesByPattern(folder, PAGE_FILE_PATTERN)
37
+ const apiFiles = discoverFilesByPattern(folder, API_FILE_PATTERN)
38
+ const taskFiles = discoverFilesByPattern(folder, TASK_FILE_PATTERN)
39
+ const resourceTemplatesFiles = discoverFilesByPattern(folder, TASK_FILE_PATTERN)
40
+
41
+ const entries = [
42
+ ...(middlewarePath ? [middlewarePath] : []),
43
+ ...(configPath ? [configPath] : []),
44
+ ...pageFiles,
45
+ ...apiFiles,
46
+ ...taskFiles,
47
+ ...resourceTemplatesFiles,
48
+ ]
49
+
50
+ return entries
51
+ }
package/cli/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { build } from './build.js'
2
+ import { build } from './build.task.js'
3
+ import { start } from './start.task.js'
3
4
 
4
5
  const [,, command, ...restArgs] = process.argv
5
6
 
6
7
  if (!command) {
7
8
  console.error(
8
- '[@ossy/app] No command provided. Usage: app build'
9
+ '[@ossy/app] No command provided. Usage: app <build|start>'
9
10
  )
10
11
  process.exit(1)
11
12
  }
@@ -15,6 +16,10 @@ const run = async () => {
15
16
  await build(restArgs)
16
17
  return
17
18
  }
19
+ if (command === 'start') {
20
+ await start(restArgs)
21
+ return
22
+ }
18
23
  console.error(`[@ossy/app] Unknown command: ${command}`)
19
24
  process.exit(1)
20
25
  }
@@ -0,0 +1,19 @@
1
+ import path from 'node:path'
2
+ import arg from 'arg'
3
+ import { startServer } from '@ossy/platform/server'
4
+
5
+ export async function start (cliArgs = []) {
6
+ const options = arg({
7
+ '--port': Number,
8
+ '-p': '--port',
9
+ '--build-dir': String,
10
+ '--cwd': String,
11
+ }, { argv: cliArgs })
12
+
13
+ const cwd = options['--cwd'] ? path.resolve(options['--cwd']) : process.cwd()
14
+ const buildDir = options['--build-dir'] || 'build'
15
+ const port = options['--port']
16
+
17
+ const { lifetime } = await startServer({ cwd, buildDir, port })
18
+ await lifetime
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.15.0",
3
+ "version": "1.15.2",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -27,15 +27,15 @@
27
27
  "@babel/eslint-parser": "^7.15.8",
28
28
  "@babel/preset-react": "^7.26.3",
29
29
  "@babel/register": "^7.25.9",
30
- "@ossy/connected-components": "^1.15.0",
31
- "@ossy/design-system": "^1.15.0",
32
- "@ossy/pages": "^1.15.0",
33
- "@ossy/platform": "^1.14.0",
34
- "@ossy/router": "^1.15.0",
35
- "@ossy/router-react": "^1.15.0",
36
- "@ossy/sdk": "^1.15.0",
37
- "@ossy/sdk-react": "^1.15.0",
38
- "@ossy/themes": "^1.15.0",
30
+ "@ossy/connected-components": "^1.15.2",
31
+ "@ossy/design-system": "^1.15.2",
32
+ "@ossy/pages": "^1.15.2",
33
+ "@ossy/platform": "^1.14.2",
34
+ "@ossy/router": "^1.15.2",
35
+ "@ossy/router-react": "^1.15.2",
36
+ "@ossy/sdk": "^1.15.2",
37
+ "@ossy/sdk-react": "^1.15.2",
38
+ "@ossy/themes": "^1.15.2",
39
39
  "@rollup/plugin-alias": "^6.0.0",
40
40
  "@rollup/plugin-babel": "6.1.0",
41
41
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -68,5 +68,5 @@
68
68
  "README.md",
69
69
  "tsconfig.json"
70
70
  ],
71
- "gitHead": "6a7aac8937521487dc9a032a8fe71f02f2a86367"
71
+ "gitHead": "6a15f3ac4cafdb190beed11e4a93adc0ba28c245"
72
72
  }
package/src/index.js CHANGED
@@ -3,4 +3,4 @@ export {
3
3
  writeResourceTemplatesBarrelIfPresent,
4
4
  resourceTemplatesDir,
5
5
  OSSY_RESOURCE_TEMPLATES_OUT,
6
- } from '../cli/build.js'
6
+ } from '../cli/build.task.js'
package/cli/Middleware.js DELETED
@@ -1,3 +0,0 @@
1
- // dummy file so it can be imported
2
-
3
- export default undefined