@ossy/app 1.15.1 → 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.
- package/cli/build.task.js +74 -0
- package/cli/get-files.task copy.js +234 -0
- package/cli/getPlatformFiles.task.js +51 -0
- package/cli/index.js +7 -2
- package/cli/start.task.js +19 -0
- package/package.json +11 -11
- package/src/index.js +1 -1
- package/cli/build.js +0 -449
|
@@ -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.
|
|
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.
|
|
31
|
-
"@ossy/design-system": "^1.15.
|
|
32
|
-
"@ossy/pages": "^1.15.
|
|
33
|
-
"@ossy/platform": "^1.14.
|
|
34
|
-
"@ossy/router": "^1.15.
|
|
35
|
-
"@ossy/router-react": "^1.15.
|
|
36
|
-
"@ossy/sdk": "^1.15.
|
|
37
|
-
"@ossy/sdk-react": "^1.15.
|
|
38
|
-
"@ossy/themes": "^1.15.
|
|
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": "
|
|
71
|
+
"gitHead": "6a15f3ac4cafdb190beed11e4a93adc0ba28c245"
|
|
72
72
|
}
|
package/src/index.js
CHANGED
package/cli/build.js
DELETED
|
@@ -1,449 +0,0 @@
|
|
|
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 { minify as minifyWithTerser } from 'terser'
|
|
15
|
-
|
|
16
|
-
export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
17
|
-
export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
|
|
18
|
-
export const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
|
|
19
|
-
const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.js$/
|
|
20
|
-
|
|
21
|
-
export const OSSY_GEN_DIRNAME = '.ossy'
|
|
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
|
-
const HYDRATE_ENTRY_FILENAME = 'hydrate-entry.jsx'
|
|
28
|
-
const SSR_ENTRY_FILENAME = 'ssr-entry.mjs'
|
|
29
|
-
|
|
30
|
-
function ossyGeneratedDir (buildPath) {
|
|
31
|
-
return path.join(buildPath, OSSY_GEN_DIRNAME)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function ensureOssyGeneratedDir (buildPath) {
|
|
35
|
-
const dir = ossyGeneratedDir(buildPath)
|
|
36
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
37
|
-
return dir
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function resetBuildDir (buildPath) {
|
|
41
|
-
fs.rmSync(buildPath, { recursive: true, force: true })
|
|
42
|
-
ensureOssyGeneratedDir(buildPath)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function relToGeneratedImport (generatedAbs, targetAbs) {
|
|
46
|
-
return path.relative(path.dirname(generatedAbs), targetAbs).replace(/\\/g, '/')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function discoverFilesByPattern (srcDir, filePattern) {
|
|
50
|
-
const dir = path.resolve(srcDir)
|
|
51
|
-
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
52
|
-
return []
|
|
53
|
-
}
|
|
54
|
-
const files = []
|
|
55
|
-
const walk = (d) => {
|
|
56
|
-
const entries = fs.readdirSync(d, { withFileTypes: true })
|
|
57
|
-
for (const e of entries) {
|
|
58
|
-
const full = path.join(d, e.name)
|
|
59
|
-
if (e.isDirectory()) walk(full)
|
|
60
|
-
else if (filePattern.test(e.name)) files.push(full)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
walk(dir)
|
|
64
|
-
return files.sort()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function buildTasksManifestPayload (taskFiles, cwd = process.cwd()) {
|
|
68
|
-
return taskFiles.map((f) => path.relative(cwd, f).replace(/\\/g, '/'))
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function parseIdPathPairsFromFile (filePath) {
|
|
72
|
-
try {
|
|
73
|
-
const content = fs.readFileSync(filePath, 'utf8')
|
|
74
|
-
const items = []
|
|
75
|
-
const idPathPattern = /\{\s*id\s*:\s*['"]([^'"]*)['"]\s*,\s*path\s*:\s*['"]([^'"]*)['"]/g
|
|
76
|
-
let m
|
|
77
|
-
while ((m = idPathPattern.exec(content)) !== null) {
|
|
78
|
-
items.push({ id: m[1], path: m[2] })
|
|
79
|
-
}
|
|
80
|
-
return items
|
|
81
|
-
} catch {
|
|
82
|
-
return []
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function writeJson (filePath, data) {
|
|
87
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
88
|
-
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function filePathToRoute (filePath, srcDir) {
|
|
92
|
-
const rel = path.relative(srcDir, filePath).replace(/\\/g, '/')
|
|
93
|
-
let pathPart = rel.replace(PAGE_FILE_PATTERN, '').replace(/\/index$/, '').replace(/\/home$/, '') || 'home'
|
|
94
|
-
if (pathPart === 'index' || pathPart === 'home') pathPart = 'home'
|
|
95
|
-
const id = pathPart === 'home' ? 'home' : pathPart.replace(/\//g, '-')
|
|
96
|
-
const routePath = pathPart === 'home' ? '/' : '/' + pathPart
|
|
97
|
-
return { id, path: routePath }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function pageRouteFromSource (pageAbsPath, srcDir) {
|
|
101
|
-
const derived = filePathToRoute(pageAbsPath, srcDir)
|
|
102
|
-
try {
|
|
103
|
-
const src = fs.readFileSync(pageAbsPath, 'utf8')
|
|
104
|
-
const metaIdx = src.indexOf('export const metadata')
|
|
105
|
-
if (metaIdx === -1) return derived
|
|
106
|
-
const after = src.slice(metaIdx)
|
|
107
|
-
const id = after.match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1] ?? derived.id
|
|
108
|
-
const strPath = after.match(/\bpath\s*:\s*['"]([^'"]+)['"]/)?.[1]
|
|
109
|
-
if (strPath) return { id, path: strPath }
|
|
110
|
-
const pathObjBody = after.match(/\bpath\s*:\s*\{([\s\S]*?)\}/)?.[1]
|
|
111
|
-
if (pathObjBody) {
|
|
112
|
-
const languagePathEntries = [...pathObjBody.matchAll(/([A-Za-z0-9_]+)\s*:\s*['"]([^'"]+)['"]/g)]
|
|
113
|
-
if (languagePathEntries.length > 0) {
|
|
114
|
-
return {
|
|
115
|
-
id,
|
|
116
|
-
path: Object.fromEntries(languagePathEntries.map(([, language, routePath]) => [language, routePath])),
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return { id, path: derived.path }
|
|
121
|
-
} catch {
|
|
122
|
-
return derived
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function createNodePlugins (nodeEnv) {
|
|
127
|
-
return [
|
|
128
|
-
replace({
|
|
129
|
-
preventAssignment: true,
|
|
130
|
-
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
131
|
-
}),
|
|
132
|
-
json(),
|
|
133
|
-
nodeExternals({
|
|
134
|
-
deps: false,
|
|
135
|
-
devDeps: true,
|
|
136
|
-
peerDeps: false,
|
|
137
|
-
packagePath: path.join(process.cwd(), 'package.json'),
|
|
138
|
-
}),
|
|
139
|
-
resolveCommonJsDependencies(),
|
|
140
|
-
resolveDependencies({ preferBuiltins: true }),
|
|
141
|
-
babel({
|
|
142
|
-
babelHelpers: 'bundled',
|
|
143
|
-
extensions: ['.jsx', '.tsx'],
|
|
144
|
-
presets: [['@babel/preset-react', { runtime: 'automatic' }]],
|
|
145
|
-
}),
|
|
146
|
-
]
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function bundleNodeEntry ({ inputPath, outputFile, nodeEnv }) {
|
|
150
|
-
const bundle = await rollup({
|
|
151
|
-
input: inputPath,
|
|
152
|
-
plugins: createNodePlugins(nodeEnv),
|
|
153
|
-
})
|
|
154
|
-
await bundle.write({
|
|
155
|
-
file: outputFile,
|
|
156
|
-
format: 'esm',
|
|
157
|
-
inlineDynamicImports: true,
|
|
158
|
-
})
|
|
159
|
-
await bundle.close()
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function generateHydrateEntry ({ pageFiles, srcDir, stubAbsPath }) {
|
|
163
|
-
const seenIds = new Set()
|
|
164
|
-
const pageLines = pageFiles.map((f) => {
|
|
165
|
-
const { id } = pageRouteFromSource(f, srcDir)
|
|
166
|
-
if (seenIds.has(id)) {
|
|
167
|
-
throw new Error(`[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`)
|
|
168
|
-
}
|
|
169
|
-
seenIds.add(id)
|
|
170
|
-
const rel = relToGeneratedImport(stubAbsPath, f)
|
|
171
|
-
return ` ${JSON.stringify(id)}: () => import('./${rel}'),`
|
|
172
|
-
})
|
|
173
|
-
return [
|
|
174
|
-
'// Generated by @ossy/app — do not edit',
|
|
175
|
-
"import { createElement } from 'react'",
|
|
176
|
-
"import { hydrateRoot } from 'react-dom/client'",
|
|
177
|
-
"import { App } from '@ossy/connected-components'",
|
|
178
|
-
'const config = window.__INITIAL_APP_CONFIG__ || {}',
|
|
179
|
-
'const pages = {',
|
|
180
|
-
...pageLines,
|
|
181
|
-
'}',
|
|
182
|
-
'const load = pages[config.pageId]',
|
|
183
|
-
'if (load) {',
|
|
184
|
-
' load().then((mod) => {',
|
|
185
|
-
' const Page = mod.default',
|
|
186
|
-
' const metadata = mod.metadata || {}',
|
|
187
|
-
' function PageShell (props) {',
|
|
188
|
-
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
189
|
-
" createElement('head', null,",
|
|
190
|
-
" createElement('meta', { charSet: 'utf-8' }),",
|
|
191
|
-
" createElement('title', null, metadata.title || ''),",
|
|
192
|
-
' ),',
|
|
193
|
-
' createElement(App, props,',
|
|
194
|
-
' createElement(Page, props)',
|
|
195
|
-
' )',
|
|
196
|
-
' )',
|
|
197
|
-
' }',
|
|
198
|
-
' hydrateRoot(document, createElement(PageShell, config))',
|
|
199
|
-
' })',
|
|
200
|
-
'}',
|
|
201
|
-
'',
|
|
202
|
-
].join('\n')
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function generateSsrEntry ({ pageFiles, srcDir, stubAbsPath }) {
|
|
206
|
-
const seenIds = new Set()
|
|
207
|
-
const pagesLiteral = []
|
|
208
|
-
const pageModuleLines = []
|
|
209
|
-
for (const f of pageFiles) {
|
|
210
|
-
const { id, path: routePath } = pageRouteFromSource(f, srcDir)
|
|
211
|
-
if (seenIds.has(id)) {
|
|
212
|
-
throw new Error(`[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`)
|
|
213
|
-
}
|
|
214
|
-
seenIds.add(id)
|
|
215
|
-
const rel = relToGeneratedImport(stubAbsPath, f)
|
|
216
|
-
pagesLiteral.push(` { id: ${JSON.stringify(id)}, path: ${JSON.stringify(routePath)} },`)
|
|
217
|
-
pageModuleLines.push(` ${JSON.stringify(id)}: () => import('./${rel}'),`)
|
|
218
|
-
}
|
|
219
|
-
return [
|
|
220
|
-
'// Generated by @ossy/app — do not edit',
|
|
221
|
-
"import { createElement } from 'react'",
|
|
222
|
-
"import { renderToPipeableStream } from 'react-dom/server'",
|
|
223
|
-
"import { Writable } from 'node:stream'",
|
|
224
|
-
"import { App } from '@ossy/connected-components'",
|
|
225
|
-
'export const pages = [',
|
|
226
|
-
...pagesLiteral,
|
|
227
|
-
']',
|
|
228
|
-
'const pageModules = {',
|
|
229
|
-
...pageModuleLines,
|
|
230
|
-
'}',
|
|
231
|
-
'function PageShell (props) {',
|
|
232
|
-
' const meta = props._pageMeta || {}',
|
|
233
|
-
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
234
|
-
" createElement('head', null,",
|
|
235
|
-
" createElement('meta', { charSet: 'utf-8' }),",
|
|
236
|
-
" createElement('title', null, meta.title || ''),",
|
|
237
|
-
' ),',
|
|
238
|
-
' createElement(App, props,',
|
|
239
|
-
' createElement(props._pageComponent, props)',
|
|
240
|
-
' )',
|
|
241
|
-
' )',
|
|
242
|
-
'}',
|
|
243
|
-
'export async function renderPage (pageId, props) {',
|
|
244
|
-
' const load = pageModules[pageId]',
|
|
245
|
-
' if (!load) throw new Error(`[@ossy/app] Unknown page id: ${pageId}`)',
|
|
246
|
-
' const mod = await load()',
|
|
247
|
-
' const merged = { ...props, _pageComponent: mod.default, _pageMeta: mod.metadata || {} }',
|
|
248
|
-
' return new Promise((resolve, reject) => {',
|
|
249
|
-
" let html = ''",
|
|
250
|
-
' const writable = new Writable({',
|
|
251
|
-
' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
|
|
252
|
-
' })',
|
|
253
|
-
' const { pipe } = renderToPipeableStream(createElement(PageShell, merged), {',
|
|
254
|
-
' onAllReady () { pipe(writable) },',
|
|
255
|
-
' onError (err) { reject(err) },',
|
|
256
|
-
' })',
|
|
257
|
-
" writable.on('finish', () => resolve(html))",
|
|
258
|
-
' })',
|
|
259
|
-
'}',
|
|
260
|
-
'',
|
|
261
|
-
].join('\n')
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function writeAppRuntimeShims ({ middlewareSourcePath, configSourcePath, ossyDir }) {
|
|
265
|
-
writeJson(path.join(ossyDir, 'middleware.runtime.json'), {
|
|
266
|
-
modulePath: path.resolve(middlewareSourcePath),
|
|
267
|
-
})
|
|
268
|
-
writeJson(path.join(ossyDir, 'server-config.runtime.json'), {
|
|
269
|
-
modulePath: path.resolve(configSourcePath),
|
|
270
|
-
})
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function minifyBrowserStaticChunks () {
|
|
274
|
-
return {
|
|
275
|
-
name: 'minify-browser-static-chunks',
|
|
276
|
-
async renderChunk (code, chunk, outputOptions) {
|
|
277
|
-
const fileName = chunk.fileName
|
|
278
|
-
if (!fileName || !fileName.startsWith('public/static/')) return null
|
|
279
|
-
const result = await minifyWithTerser(code, {
|
|
280
|
-
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string',
|
|
281
|
-
module: outputOptions.format === 'es' || outputOptions.format === 'esm',
|
|
282
|
-
})
|
|
283
|
-
return result.code ?? code
|
|
284
|
-
},
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function compileCombinedBundle ({ ssrEntryPath, clientEntryPath, buildPath, nodeEnv, copyPublicFrom }) {
|
|
289
|
-
if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
|
|
290
|
-
fs.cpSync(copyPublicFrom, path.join(buildPath, 'public'), { recursive: true, force: true })
|
|
291
|
-
}
|
|
292
|
-
const bundle = await rollup({
|
|
293
|
-
input: { server: ssrEntryPath, app: clientEntryPath },
|
|
294
|
-
plugins: createNodePlugins(nodeEnv),
|
|
295
|
-
})
|
|
296
|
-
await bundle.write({
|
|
297
|
-
dir: buildPath,
|
|
298
|
-
format: 'esm',
|
|
299
|
-
entryFileNames: (chunk) => (chunk.name === 'server' ? 'ssr/app.mjs' : 'public/static/app.js'),
|
|
300
|
-
chunkFileNames: 'public/static/chunks/[name]-[hash].js',
|
|
301
|
-
plugins: [minifyBrowserStaticChunks()],
|
|
302
|
-
})
|
|
303
|
-
await bundle.close()
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async function compileApiServerModules ({ apiFiles, ossyDir, nodeEnv }) {
|
|
307
|
-
const modsDir = path.join(ossyDir, 'api-modules')
|
|
308
|
-
fs.rmSync(modsDir, { recursive: true, force: true })
|
|
309
|
-
fs.mkdirSync(modsDir, { recursive: true })
|
|
310
|
-
const routes = []
|
|
311
|
-
for (let i = 0; i < apiFiles.length; i++) {
|
|
312
|
-
const outName = `api-${i}.mjs`
|
|
313
|
-
const outFile = path.join(modsDir, outName)
|
|
314
|
-
await bundleNodeEntry({ inputPath: apiFiles[i], outputFile: outFile, nodeEnv })
|
|
315
|
-
let meta = {}
|
|
316
|
-
try {
|
|
317
|
-
const mod = await import(pathToFileURL(outFile).href)
|
|
318
|
-
meta = mod?.metadata && typeof mod.metadata === 'object' ? mod.metadata : {}
|
|
319
|
-
} catch {}
|
|
320
|
-
routes.push({ ...meta, module: `api-modules/${outName}` })
|
|
321
|
-
}
|
|
322
|
-
return routes
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
async function compileTaskServerModules ({ taskFiles, ossyDir, nodeEnv }) {
|
|
326
|
-
const modsDir = path.join(ossyDir, 'task-modules')
|
|
327
|
-
fs.rmSync(modsDir, { recursive: true, force: true })
|
|
328
|
-
fs.mkdirSync(modsDir, { recursive: true })
|
|
329
|
-
const tasks = []
|
|
330
|
-
for (let i = 0; i < taskFiles.length; i++) {
|
|
331
|
-
const outName = `task-${i}.mjs`
|
|
332
|
-
const outFile = path.join(modsDir, outName)
|
|
333
|
-
await bundleNodeEntry({ inputPath: taskFiles[i], outputFile: outFile, nodeEnv })
|
|
334
|
-
let meta = {}
|
|
335
|
-
try {
|
|
336
|
-
const mod = await import(pathToFileURL(outFile).href)
|
|
337
|
-
meta = mod?.metadata && typeof mod.metadata === 'object' ? mod.metadata : {}
|
|
338
|
-
} catch {}
|
|
339
|
-
tasks.push({ ...meta, module: `task-modules/${outName}` })
|
|
340
|
-
}
|
|
341
|
-
return tasks
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function writeGeneratedEntries ({ pageFiles, srcDir, ossyDir }) {
|
|
345
|
-
const ssrEntryPath = path.join(ossyDir, SSR_ENTRY_FILENAME)
|
|
346
|
-
const hydrateEntryPath = path.join(ossyDir, HYDRATE_ENTRY_FILENAME)
|
|
347
|
-
fs.writeFileSync(ssrEntryPath, generateSsrEntry({ pageFiles, srcDir, stubAbsPath: ssrEntryPath }))
|
|
348
|
-
fs.writeFileSync(hydrateEntryPath, generateHydrateEntry({ pageFiles, srcDir, stubAbsPath: hydrateEntryPath }))
|
|
349
|
-
return { ssrEntryPath, hydrateEntryPath }
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function copyPlatformRuntime ({ buildPath }) {
|
|
353
|
-
const require = createRequire(import.meta.url)
|
|
354
|
-
const platformServerPath = require.resolve('@ossy/platform/server')
|
|
355
|
-
const platformWorkerPath = require.resolve('@ossy/platform/worker')
|
|
356
|
-
const platformServerDir = path.dirname(platformServerPath)
|
|
357
|
-
const platformWorkerDir = path.dirname(platformWorkerPath)
|
|
358
|
-
|
|
359
|
-
for (const name of ['server.js', 'proxy-internal.js']) {
|
|
360
|
-
fs.copyFileSync(path.join(platformServerDir, name), path.join(buildPath, name))
|
|
361
|
-
}
|
|
362
|
-
fs.copyFileSync(path.join(platformWorkerDir, 'worker-entry.js'), path.join(buildPath, 'worker.js'))
|
|
363
|
-
fs.copyFileSync(path.join(platformWorkerDir, 'worker-runtime.js'), path.join(buildPath, 'worker-runtime.js'))
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export function resourceTemplatesDir (cwd = process.cwd()) {
|
|
367
|
-
return path.join(cwd, 'src', 'resource-templates')
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function discoverResourceTemplateFiles (templatesDir) {
|
|
371
|
-
if (!fs.existsSync(templatesDir) || !fs.statSync(templatesDir).isDirectory()) return []
|
|
372
|
-
return fs
|
|
373
|
-
.readdirSync(templatesDir)
|
|
374
|
-
.filter((n) => RESOURCE_TEMPLATE_FILE_PATTERN.test(n))
|
|
375
|
-
.map((n) => path.join(templatesDir, n))
|
|
376
|
-
.sort((a, b) => path.basename(a).localeCompare(path.basename(b)))
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export function generateResourceTemplatesBarrelSource ({ outputAbs, templateFilesAbs }) {
|
|
380
|
-
const payload = templateFilesAbs.map((f) => relToGeneratedImport(outputAbs, f))
|
|
381
|
-
return `${JSON.stringify(payload, null, 2)}\n`
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function writeResourceTemplatesBarrelIfPresent ({ cwd = process.cwd(), log = true } = {}) {
|
|
385
|
-
const dir = resourceTemplatesDir(cwd)
|
|
386
|
-
if (!fs.existsSync(dir)) {
|
|
387
|
-
return { wrote: false, count: 0, path: null }
|
|
388
|
-
}
|
|
389
|
-
const files = discoverResourceTemplateFiles(dir)
|
|
390
|
-
const outAbs = path.join(dir, OSSY_RESOURCE_TEMPLATES_OUT)
|
|
391
|
-
fs.writeFileSync(outAbs, generateResourceTemplatesBarrelSource({ outputAbs: outAbs, templateFilesAbs: files }), 'utf8')
|
|
392
|
-
if (log) {
|
|
393
|
-
console.log(`[@ossy/app][resource-templates] merged ${files.length} template(s) → ${path.relative(cwd, outAbs)}`)
|
|
394
|
-
}
|
|
395
|
-
return { wrote: true, count: files.length, path: outAbs }
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export async function build (cliArgs = []) {
|
|
399
|
-
const options = arg({
|
|
400
|
-
'--config': String,
|
|
401
|
-
'-c': '--config',
|
|
402
|
-
}, { argv: cliArgs })
|
|
403
|
-
|
|
404
|
-
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
405
|
-
const cwd = process.cwd()
|
|
406
|
-
const buildPath = path.resolve(cwd, 'build')
|
|
407
|
-
const srcDir = path.resolve(cwd, 'src')
|
|
408
|
-
const publicDir = path.resolve(cwd, 'public')
|
|
409
|
-
const configPath = path.resolve(cwd, options['--config'] || 'src/config.js')
|
|
410
|
-
|
|
411
|
-
resetBuildDir(buildPath)
|
|
412
|
-
writeResourceTemplatesBarrelIfPresent({ cwd, log: false })
|
|
413
|
-
const ossyDir = ossyGeneratedDir(buildPath)
|
|
414
|
-
|
|
415
|
-
const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
|
|
416
|
-
const apiFiles = discoverFilesByPattern(srcDir, API_FILE_PATTERN)
|
|
417
|
-
const taskFiles = discoverFilesByPattern(srcDir, TASK_FILE_PATTERN)
|
|
418
|
-
|
|
419
|
-
writeJson(
|
|
420
|
-
path.join(ossyDir, OSSY_GEN_PAGES_BASENAME),
|
|
421
|
-
pageFiles.map((f) => {
|
|
422
|
-
const { id, path: routePath } = pageRouteFromSource(f, srcDir)
|
|
423
|
-
return { id, path: routePath, sourceFile: path.relative(cwd, f).replace(/\\/g, '/') }
|
|
424
|
-
})
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
const apiRouteList = await compileApiServerModules({ apiFiles, ossyDir, nodeEnv: 'production' })
|
|
428
|
-
const taskRouteList = await compileTaskServerModules({ taskFiles, ossyDir, nodeEnv: 'production' })
|
|
429
|
-
writeJson(path.join(ossyDir, OSSY_GEN_API_BASENAME), apiRouteList)
|
|
430
|
-
writeJson(path.join(ossyDir, OSSY_GEN_TASKS_BASENAME), taskRouteList)
|
|
431
|
-
|
|
432
|
-
const middlewareSourcePath = fs.existsSync(path.resolve(cwd, 'src/middleware.js'))
|
|
433
|
-
? path.resolve(cwd, 'src/middleware.js')
|
|
434
|
-
: path.resolve(scriptDir, 'Middleware.js')
|
|
435
|
-
const configSourcePath = fs.existsSync(configPath)
|
|
436
|
-
? configPath
|
|
437
|
-
: path.resolve(scriptDir, 'default-config.js')
|
|
438
|
-
writeAppRuntimeShims({ middlewareSourcePath, configSourcePath, ossyDir })
|
|
439
|
-
|
|
440
|
-
const { ssrEntryPath, hydrateEntryPath } = writeGeneratedEntries({ pageFiles, srcDir, ossyDir })
|
|
441
|
-
copyPlatformRuntime({ buildPath })
|
|
442
|
-
await compileCombinedBundle({
|
|
443
|
-
ssrEntryPath,
|
|
444
|
-
clientEntryPath: hydrateEntryPath,
|
|
445
|
-
buildPath,
|
|
446
|
-
nodeEnv: 'production',
|
|
447
|
-
copyPublicFrom: publicDir,
|
|
448
|
-
})
|
|
449
|
-
}
|