@ossy/app 1.0.8 → 1.1.0

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.js CHANGED
@@ -11,12 +11,10 @@ import nodeExternals from 'rollup-plugin-node-externals'
11
11
  import copy from 'rollup-plugin-copy';
12
12
  import replace from '@rollup/plugin-replace';
13
13
  import arg from 'arg'
14
- import { ensureBuildStubs } from '../scripts/ensure-build-stubs.mjs'
15
- import { createRequire } from 'node:module'
16
14
 
17
- const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
18
- const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
19
- const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
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)$/
20
18
  const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.js$/
21
19
 
22
20
  /** Written next to `*.resource.js` under `src/resource-templates/` when that dir exists. */
@@ -57,6 +55,11 @@ export const OSSY_GEN_PAGES_BASENAME = 'pages.generated.jsx'
57
55
  export const OSSY_GEN_API_BASENAME = 'api.generated.js'
58
56
  export const OSSY_GEN_TASKS_BASENAME = 'tasks.generated.js'
59
57
 
58
+ /** Bundled Node entries consumed by copied `build/server.js` / `build/worker.js`. */
59
+ export const OSSY_PAGES_SERVER_BUNDLE = 'pages.bundle.js'
60
+ export const OSSY_API_SERVER_BUNDLE = 'api.bundle.js'
61
+ export const OSSY_TASKS_SERVER_BUNDLE = 'tasks.bundle.js'
62
+
60
63
  /** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
61
64
  const HYDRATE_STUB_PREFIX = 'hydrate-'
62
65
  const HYDRATE_STUB_SUFFIX = '.jsx'
@@ -144,65 +147,55 @@ export function writeResourceTemplatesBarrelIfPresent ({ cwd = process.cwd(), lo
144
147
  }
145
148
 
146
149
  /**
147
- * Shared Rollup plugins (virtual path replaces, resolve, JSX).
148
- * Server builds pass `nodeExternals: true` so `dependencies` stay importable from Node at runtime.
150
+ * Rollup plugins for Node-side bundles (pages / API / tasks): externals + Babel JSX.
149
151
  */
150
- export function createOssyRollupPlugins ({
151
- pagesGeneratedPath,
152
- apiSourcePath,
153
- middlewareSourcePath,
154
- configSourcePath,
155
- nodeEnv,
156
- preferBuiltins = true,
157
- copyPublicFrom,
158
- buildPath,
159
- }) {
160
- const plugins = [
161
- replace({
162
- preventAssignment: true,
163
- delimiters: ['%%', '%%'],
164
- '@ossy/pages/source-file': pagesGeneratedPath,
165
- }),
152
+ export function createOssyAppBundlePlugins ({ nodeEnv }) {
153
+ return [
166
154
  replace({
167
155
  preventAssignment: true,
168
- delimiters: ['%%', '%%'],
169
- '@ossy/api/source-file': apiSourcePath,
156
+ 'process.env.NODE_ENV': JSON.stringify(nodeEnv),
170
157
  }),
171
- replace({
172
- preventAssignment: true,
173
- delimiters: ['%%', '%%'],
174
- '@ossy/middleware/source-file': middlewareSourcePath,
158
+ json(),
159
+ nodeExternals({
160
+ deps: true,
161
+ devDeps: false,
162
+ peerDeps: true,
163
+ packagePath: path.join(process.cwd(), 'package.json'),
175
164
  }),
176
- replace({
177
- preventAssignment: true,
178
- delimiters: ['%%', '%%'],
179
- '@ossy/config/source-file': configSourcePath,
165
+ resolveCommonJsDependencies(),
166
+ resolveDependencies({ preferBuiltins: true }),
167
+ babel({
168
+ babelHelpers: 'bundled',
169
+ extensions: ['.jsx', '.tsx'],
170
+ presets: ['@babel/preset-react'],
180
171
  }),
172
+ ]
173
+ }
174
+
175
+ /**
176
+ * Rollup plugins for browser hydrate bundles.
177
+ */
178
+ export function createOssyClientRollupPlugins ({ nodeEnv, copyPublicFrom, buildPath }) {
179
+ const plugins = [
181
180
  replace({
182
181
  preventAssignment: true,
183
182
  'process.env.NODE_ENV': JSON.stringify(nodeEnv),
184
183
  }),
185
184
  json(),
186
- ]
187
-
188
- plugins.push(
189
185
  nodeExternals({
190
186
  deps: true,
191
187
  devDeps: false,
192
188
  peerDeps: true,
193
189
  packagePath: path.join(process.cwd(), 'package.json'),
194
- })
195
- )
196
-
197
- plugins.push(
190
+ }),
198
191
  resolveCommonJsDependencies(),
199
- resolveDependencies({ preferBuiltins }),
192
+ resolveDependencies({ preferBuiltins: false }),
200
193
  babel({
201
194
  babelHelpers: 'bundled',
202
195
  extensions: ['.jsx', '.tsx'],
203
196
  presets: ['@babel/preset-react'],
204
- })
205
- )
197
+ }),
198
+ ]
206
199
  if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
207
200
  plugins.push(
208
201
  copy({
@@ -213,25 +206,43 @@ export function createOssyRollupPlugins ({
213
206
  return plugins
214
207
  }
215
208
 
216
- export function discoverApiFiles(srcDir) {
217
- const dir = path.resolve(srcDir)
218
- if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
219
- return []
220
- }
221
- const files = []
222
- const walk = (d) => {
223
- const entries = fs.readdirSync(d, { withFileTypes: true })
224
- for (const e of entries) {
225
- const full = path.join(d, e.name)
226
- if (e.isDirectory()) walk(full)
227
- else if (API_FILE_PATTERN.test(e.name)) files.push(full)
228
- }
209
+ /** Bundles a single Node ESM file (inline dynamic imports) for SSR / API / tasks. */
210
+ export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv }) {
211
+ const bundle = await rollup({
212
+ input: inputPath,
213
+ plugins: createOssyAppBundlePlugins({ nodeEnv }),
214
+ })
215
+ await bundle.write({
216
+ file: outputFile,
217
+ format: 'esm',
218
+ inlineDynamicImports: true,
219
+ })
220
+ await bundle.close()
221
+ }
222
+
223
+ /**
224
+ * Copies framework runtime into `build/`: server, worker entry, middleware & config snapshots.
225
+ * `build/server.js` imports `./middleware.js`, `./config.js`, `./.ossy/*.bundle.js` next to it.
226
+ */
227
+ export function copyOssyAppRuntime ({
228
+ scriptDir,
229
+ buildPath,
230
+ middlewareSourcePath,
231
+ configSourcePath,
232
+ }) {
233
+ for (const name of ['server.js', 'proxy-internal.js']) {
234
+ fs.copyFileSync(path.join(scriptDir, name), path.join(buildPath, name))
229
235
  }
230
- walk(dir)
231
- return files.sort()
236
+ fs.copyFileSync(middlewareSourcePath, path.join(buildPath, 'middleware.js'))
237
+ fs.copyFileSync(configSourcePath, path.join(buildPath, 'config.js'))
238
+ fs.copyFileSync(path.join(scriptDir, 'worker-entry.js'), path.join(buildPath, 'worker.js'))
239
+ fs.copyFileSync(path.join(scriptDir, 'worker-runtime.js'), path.join(buildPath, 'worker-runtime.js'))
232
240
  }
233
241
 
234
- export function discoverTaskFiles(srcDir) {
242
+ /**
243
+ * Recursively lists files under `srcDir` whose basename matches `filePattern` (e.g. `/\.api\.js$/`).
244
+ */
245
+ export function discoverFilesByPattern (srcDir, filePattern) {
235
246
  const dir = path.resolve(srcDir)
236
247
  if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
237
248
  return []
@@ -242,7 +253,7 @@ export function discoverTaskFiles(srcDir) {
242
253
  for (const e of entries) {
243
254
  const full = path.join(d, e.name)
244
255
  if (e.isDirectory()) walk(full)
245
- else if (TASK_FILE_PATTERN.test(e.name)) files.push(full)
256
+ else if (filePattern.test(e.name)) files.push(full)
246
257
  }
247
258
  }
248
259
  walk(dir)
@@ -294,7 +305,7 @@ export function generateApiModule ({ generatedPath, apiFiles }) {
294
305
  export function resolveApiSource ({ srcDir, buildPath }) {
295
306
  ensureOssyGeneratedDir(buildPath)
296
307
  const generatedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_API_BASENAME)
297
- const apiFiles = discoverApiFiles(srcDir)
308
+ const apiFiles = discoverFilesByPattern(srcDir, API_FILE_PATTERN)
298
309
  fs.writeFileSync(
299
310
  generatedPath,
300
311
  generateApiModule({ generatedPath, apiFiles })
@@ -336,43 +347,20 @@ export function generateTaskModule ({ generatedPath, taskFiles }) {
336
347
  }
337
348
 
338
349
  /**
339
- * Resolves the Rollup entry for @ossy/tasks/source-file and which files to list in the worker build overview.
350
+ * Writes `build/.ossy/tasks.generated.js` and returns its path (stable Rollup input; empty when no task files).
340
351
  */
341
- export function resolveTaskSource ({ srcDir, scriptDir, buildPath }) {
342
- const defaultStub = path.resolve(scriptDir, 'tasks.js')
352
+ export function resolveTaskSource ({ srcDir, buildPath }) {
343
353
  ensureOssyGeneratedDir(buildPath)
344
354
  const generatedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_TASKS_BASENAME)
345
-
346
- const taskFiles = discoverTaskFiles(srcDir)
347
- if (taskFiles.length > 0) {
348
- fs.writeFileSync(
355
+ const taskFiles = discoverFilesByPattern(srcDir, TASK_FILE_PATTERN)
356
+ fs.writeFileSync(
357
+ generatedPath,
358
+ generateTaskModule({
349
359
  generatedPath,
350
- generateTaskModule({
351
- generatedPath,
352
- taskFiles,
353
- })
354
- )
355
- return { taskSourcePath: generatedPath, taskOverviewFiles: taskFiles, usedGenerated: true }
356
- }
357
-
358
- return { taskSourcePath: defaultStub, taskOverviewFiles: [], usedGenerated: false }
359
- }
360
-
361
- export function discoverPageFiles(srcDir) {
362
- if (!fs.existsSync(srcDir) || !fs.statSync(srcDir).isDirectory()) {
363
- return []
364
- }
365
- const files = []
366
- const walk = (d) => {
367
- const entries = fs.readdirSync(d, { withFileTypes: true })
368
- for (const e of entries) {
369
- const full = path.join(d, e.name)
370
- if (e.isDirectory()) walk(full)
371
- else if (PAGE_FILE_PATTERN.test(e.name)) files.push(full)
372
- }
373
- }
374
- walk(srcDir)
375
- return files.sort()
360
+ taskFiles,
361
+ })
362
+ )
363
+ return { taskSourcePath: generatedPath, taskOverviewFiles: taskFiles }
376
364
  }
377
365
 
378
366
  export function filePathToRoute(filePath, srcDir) {
@@ -510,65 +498,6 @@ export function generatePagesModule (pageFiles, srcDir, generatedPath) {
510
498
  return lines.join('\n')
511
499
  }
512
500
 
513
- export async function discoverModulePageFiles({ configPath }) {
514
- if (!configPath || !fs.existsSync(configPath)) return []
515
- try {
516
- // Try a cheap static parse first so we don't depend on the config file being
517
- // importable (configs often import theme/template modules that may not be
518
- // resolvable in the build-time node context).
519
- const cfgSource = fs.readFileSync(configPath, 'utf8')
520
- const modules = []
521
-
522
- // pagesModules: ['a', "b"]
523
- const arrMatch = cfgSource.match(/pagesModules\s*:\s*\[([^\]]*)\]/m)
524
- if (arrMatch?.[1]) {
525
- const body = arrMatch[1]
526
- const re = /['"]([^'"]+)['"]/g
527
- let m
528
- while ((m = re.exec(body)) !== null) modules.push(m[1])
529
- }
530
-
531
- // pagesModule: 'a'
532
- if (modules.length === 0) {
533
- const singleMatch = cfgSource.match(/pagesModule\s*:\s*['"]([^'"]+)['"]/m)
534
- if (singleMatch?.[1]) modules.push(singleMatch[1])
535
- }
536
-
537
- if (modules.length) {
538
- const req = createRequire(path.resolve(process.cwd(), 'package.json'))
539
- const files = []
540
- for (const name of modules) {
541
- const pkgJsonPath = req.resolve(`${name}/package.json`)
542
- const pkgDir = path.dirname(pkgJsonPath)
543
- const modulePagesDir = path.join(pkgDir, 'src', 'pages')
544
- files.push(...discoverPageFiles(modulePagesDir))
545
- }
546
- return files
547
- }
548
-
549
- const mod = await import(url.pathToFileURL(configPath))
550
- const cfg = mod?.default ?? mod ?? {}
551
- const modules2 = Array.isArray(cfg.pagesModules)
552
- ? cfg.pagesModules
553
- : (typeof cfg.pagesModule === 'string' ? [cfg.pagesModule] : [])
554
-
555
- if (!modules2.length) return []
556
-
557
- const req = createRequire(path.resolve(process.cwd(), 'package.json'))
558
- const files = []
559
- for (const name of modules2) {
560
- const pkgJsonPath = req.resolve(`${name}/package.json`)
561
- const pkgDir = path.dirname(pkgJsonPath)
562
- const modulePagesDir = path.join(pkgDir, 'src', 'pages')
563
- files.push(...discoverPageFiles(modulePagesDir))
564
- }
565
- return files
566
- } catch (e) {
567
- console.warn('[@ossy/app][build] pagesModules config could not be loaded; continuing without modules')
568
- return []
569
- }
570
- }
571
-
572
501
  export function parsePagesFromSource(filePath) {
573
502
  try {
574
503
  const content = fs.readFileSync(filePath, 'utf8')
@@ -665,8 +594,7 @@ export const build = async (cliArgs) => {
665
594
  const buildPath = path.resolve('build')
666
595
  const srcDir = path.resolve('src')
667
596
  const configPath = path.resolve(options['--config'] || 'src/config.js');
668
- const pageFiles = discoverPageFiles(srcDir)
669
- const modulePageFiles = await discoverModulePageFiles({ configPath })
597
+ const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
670
598
 
671
599
  resetOssyBuildDir(buildPath)
672
600
 
@@ -674,14 +602,13 @@ export const build = async (cliArgs) => {
674
602
 
675
603
  const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
676
604
 
677
- const allPageFiles = [...pageFiles, ...modulePageFiles]
678
605
  fs.writeFileSync(
679
606
  pagesGeneratedPath,
680
- generatePagesModule(allPageFiles, srcDir, pagesGeneratedPath)
607
+ generatePagesModule(pageFiles, srcDir, pagesGeneratedPath)
681
608
  )
682
609
  const ossyDir = ossyGeneratedDir(buildPath)
683
- writePageHydrateStubs(allPageFiles, srcDir, ossyDir)
684
- const clientHydrateInput = buildClientHydrateInput(allPageFiles, srcDir, ossyDir)
610
+ writePageHydrateStubs(pageFiles, srcDir, ossyDir)
611
+ const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
685
612
 
686
613
  const {
687
614
  apiSourcePath: resolvedApi,
@@ -694,8 +621,6 @@ export const build = async (cliArgs) => {
694
621
  let middlewareSourcePath = path.resolve('src/middleware.js');
695
622
  const publicDir = path.resolve('public')
696
623
 
697
- const inputServer = path.resolve(scriptDir, 'server.js')
698
-
699
624
  printBuildOverview({
700
625
  pagesSourcePath: pagesGeneratedPath,
701
626
  apiSourcePath,
@@ -712,40 +637,38 @@ export const build = async (cliArgs) => {
712
637
  ? configPath
713
638
  : path.resolve(scriptDir, 'default-config.js')
714
639
 
715
- const sharedPluginOpts = {
716
- pagesGeneratedPath,
717
- apiSourcePath,
718
- middlewareSourcePath,
719
- configSourcePath,
720
- nodeEnv: 'production',
721
- buildPath,
722
- }
640
+ const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_SERVER_BUNDLE)
641
+ const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
642
+ const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
723
643
 
724
- const serverPlugins = createOssyRollupPlugins({
725
- ...sharedPluginOpts,
726
- preferBuiltins: true,
727
- copyPublicFrom: publicDir,
644
+ await bundleOssyNodeEntry({
645
+ inputPath: pagesGeneratedPath,
646
+ outputFile: pagesBundlePath,
647
+ nodeEnv: 'production',
728
648
  })
729
-
730
- const serverBundle = await rollup({
731
- input: { server: inputServer },
732
- plugins: serverPlugins,
649
+ await bundleOssyNodeEntry({
650
+ inputPath: apiSourcePath,
651
+ outputFile: apiBundlePath,
652
+ nodeEnv: 'production',
733
653
  })
734
- await serverBundle.write({
735
- dir: buildPath,
736
- format: 'esm',
737
- preserveModules: true,
738
- preserveModulesRoot: path.dirname(inputServer),
739
- entryFileNames ({ name }) {
740
- return name === 'server' ? 'server.js' : '[name].js'
741
- },
742
- assetFileNames: '[name][extname]',
654
+ const { taskSourcePath } = resolveTaskSource({ srcDir, buildPath })
655
+ await bundleOssyNodeEntry({
656
+ inputPath: taskSourcePath,
657
+ outputFile: tasksBundlePath,
658
+ nodeEnv: 'production',
743
659
  })
744
- await serverBundle.close()
745
660
 
746
- const clientPlugins = createOssyRollupPlugins({
747
- ...sharedPluginOpts,
748
- preferBuiltins: false,
661
+ copyOssyAppRuntime({
662
+ scriptDir,
663
+ buildPath,
664
+ middlewareSourcePath,
665
+ configSourcePath,
666
+ })
667
+
668
+ const clientPlugins = createOssyClientRollupPlugins({
669
+ nodeEnv: 'production',
670
+ copyPublicFrom: publicDir,
671
+ buildPath,
749
672
  })
750
673
 
751
674
  if (Object.keys(clientHydrateInput).length > 0) {
@@ -769,7 +692,5 @@ export const build = async (cliArgs) => {
769
692
  await clientBundle.close()
770
693
  }
771
694
 
772
- ensureBuildStubs(buildPath)
773
-
774
695
  console.log('[@ossy/app][build] Finished');
775
696
  };
package/cli/dev.js CHANGED
@@ -3,18 +3,26 @@ import url from 'url';
3
3
  import fs from 'fs';
4
4
  import {
5
5
  printBuildOverview,
6
- discoverPageFiles,
6
+ discoverFilesByPattern,
7
+ PAGE_FILE_PATTERN,
7
8
  generatePagesModule,
8
- discoverModulePageFiles,
9
9
  resolveApiSource,
10
+ resolveTaskSource,
10
11
  resetOssyBuildDir,
11
- createOssyRollupPlugins,
12
+ bundleOssyNodeEntry,
13
+ copyOssyAppRuntime,
14
+ createOssyAppBundlePlugins,
15
+ createOssyClientRollupPlugins,
12
16
  writePageHydrateStubs,
13
17
  buildClientHydrateInput,
14
18
  clientHydrateIdForPage,
15
19
  ossyGeneratedDir,
16
20
  OSSY_GEN_PAGES_BASENAME,
17
21
  OSSY_GEN_API_BASENAME,
22
+ OSSY_GEN_TASKS_BASENAME,
23
+ OSSY_PAGES_SERVER_BUNDLE,
24
+ OSSY_API_SERVER_BUNDLE,
25
+ OSSY_TASKS_SERVER_BUNDLE,
18
26
  writeResourceTemplatesBarrelIfPresent,
19
27
  resourceTemplatesDir,
20
28
  OSSY_RESOURCE_TEMPLATES_OUT,
@@ -37,23 +45,20 @@ export const dev = async (cliArgs) => {
37
45
  const buildPath = path.resolve('build')
38
46
  const srcDir = path.resolve('src')
39
47
  const configPath = path.resolve(options['--config'] || 'src/config.js');
40
- const pageFiles = discoverPageFiles(srcDir)
41
- const modulePageFiles = await discoverModulePageFiles({ configPath })
48
+ const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
42
49
 
43
50
  resetOssyBuildDir(buildPath)
44
51
 
45
52
  writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: true })
46
53
 
47
- const allPageFiles = [...pageFiles, ...modulePageFiles]
48
54
  const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
49
55
  fs.writeFileSync(
50
56
  pagesGeneratedPath,
51
- generatePagesModule(allPageFiles, srcDir, pagesGeneratedPath)
57
+ generatePagesModule(pageFiles, srcDir, pagesGeneratedPath)
52
58
  )
53
- const effectivePagesSource = pagesGeneratedPath
54
59
  const ossyDir = ossyGeneratedDir(buildPath)
55
- writePageHydrateStubs(allPageFiles, srcDir, ossyDir)
56
- const clientHydrateInput = buildClientHydrateInput(allPageFiles, srcDir, ossyDir)
60
+ writePageHydrateStubs(pageFiles, srcDir, ossyDir)
61
+ const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
57
62
 
58
63
  const {
59
64
  apiSourcePath: resolvedApi,
@@ -63,13 +68,12 @@ export const dev = async (cliArgs) => {
63
68
  buildPath,
64
69
  })
65
70
  let apiSourcePath = resolvedApi
71
+ resolveTaskSource({ srcDir, buildPath })
66
72
  let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
67
73
  const publicDir = path.resolve('public')
68
74
 
69
- const inputServer = path.resolve(scriptDir, 'server.js')
70
-
71
75
  printBuildOverview({
72
- pagesSourcePath: effectivePagesSource,
76
+ pagesSourcePath: pagesGeneratedPath,
73
77
  apiSourcePath,
74
78
  apiOverviewFiles,
75
79
  configPath,
@@ -84,37 +88,43 @@ export const dev = async (cliArgs) => {
84
88
  ? configPath
85
89
  : path.resolve(scriptDir, 'default-config.js')
86
90
 
87
- const sharedPluginOpts = {
88
- pagesGeneratedPath: effectivePagesSource,
89
- apiSourcePath,
90
- middlewareSourcePath,
91
- configSourcePath,
92
- nodeEnv: 'development',
93
- buildPath,
91
+ const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_SERVER_BUNDLE)
92
+ const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
93
+ const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
94
+ const tasksGeneratedPath = path.join(ossyDir, OSSY_GEN_TASKS_BASENAME)
95
+
96
+ const runNodeBundles = async () => {
97
+ await bundleOssyNodeEntry({
98
+ inputPath: pagesGeneratedPath,
99
+ outputFile: pagesBundlePath,
100
+ nodeEnv: 'development',
101
+ })
102
+ await bundleOssyNodeEntry({
103
+ inputPath: apiSourcePath,
104
+ outputFile: apiBundlePath,
105
+ nodeEnv: 'development',
106
+ })
107
+ await bundleOssyNodeEntry({
108
+ inputPath: tasksGeneratedPath,
109
+ outputFile: tasksBundlePath,
110
+ nodeEnv: 'development',
111
+ })
112
+ copyOssyAppRuntime({
113
+ scriptDir,
114
+ buildPath,
115
+ middlewareSourcePath,
116
+ configSourcePath,
117
+ })
94
118
  }
95
119
 
96
- const serverPlugins = createOssyRollupPlugins({
97
- ...sharedPluginOpts,
98
- preferBuiltins: true,
99
- copyPublicFrom: publicDir,
100
- })
120
+ await runNodeBundles()
101
121
 
102
- const clientPlugins = createOssyRollupPlugins({
103
- ...sharedPluginOpts,
104
- preferBuiltins: false,
122
+ const clientPlugins = createOssyClientRollupPlugins({
123
+ nodeEnv: 'development',
124
+ copyPublicFrom: publicDir,
125
+ buildPath,
105
126
  })
106
127
 
107
- const serverOutput = {
108
- dir: buildPath,
109
- format: 'esm',
110
- preserveModules: true,
111
- preserveModulesRoot: path.dirname(inputServer),
112
- entryFileNames ({ name }) {
113
- return name === 'server' ? 'server.js' : '[name].js'
114
- },
115
- assetFileNames: '[name][extname]',
116
- }
117
-
118
128
  const clientOutput = {
119
129
  dir: buildPath,
120
130
  format: 'esm',
@@ -169,12 +179,37 @@ export const dev = async (cliArgs) => {
169
179
  }
170
180
  }
171
181
 
182
+ const nodeWatchOpts = { watch: { clearScreen: false } }
172
183
  const watchConfigs = [
173
184
  {
174
- input: { server: inputServer },
175
- output: serverOutput,
176
- plugins: serverPlugins,
177
- watch: { clearScreen: false },
185
+ input: pagesGeneratedPath,
186
+ output: {
187
+ file: pagesBundlePath,
188
+ format: 'esm',
189
+ inlineDynamicImports: true,
190
+ },
191
+ plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
192
+ ...nodeWatchOpts,
193
+ },
194
+ {
195
+ input: apiSourcePath,
196
+ output: {
197
+ file: apiBundlePath,
198
+ format: 'esm',
199
+ inlineDynamicImports: true,
200
+ },
201
+ plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
202
+ ...nodeWatchOpts,
203
+ },
204
+ {
205
+ input: tasksGeneratedPath,
206
+ output: {
207
+ file: tasksBundlePath,
208
+ format: 'esm',
209
+ inlineDynamicImports: true,
210
+ },
211
+ plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
212
+ ...nodeWatchOpts,
178
213
  },
179
214
  ]
180
215
  if (Object.keys(clientHydrateInput).length > 0) {
@@ -198,11 +233,17 @@ export const dev = async (cliArgs) => {
198
233
  console.log(`[@ossy/app][dev] Built in ${event.duration}ms`)
199
234
  }
200
235
  if (event.code === 'END') {
236
+ copyOssyAppRuntime({
237
+ scriptDir,
238
+ buildPath,
239
+ middlewareSourcePath,
240
+ configSourcePath,
241
+ })
201
242
  scheduleRestart()
202
243
  }
203
244
  })
204
245
 
205
- const regenApiBundle = () => {
246
+ const regenApiGenerated = () => {
206
247
  if (options['--api-source']) return
207
248
  resolveApiSource({
208
249
  srcDir,
@@ -214,23 +255,33 @@ export const dev = async (cliArgs) => {
214
255
  }
215
256
  }
216
257
 
258
+ const regenTasksGenerated = () => {
259
+ resolveTaskSource({ srcDir, buildPath })
260
+ if (fs.existsSync(tasksGeneratedPath) && typeof watcher?.invalidate === 'function') {
261
+ watcher.invalidate(tasksGeneratedPath)
262
+ }
263
+ }
264
+
217
265
  fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
218
266
  if (!filename) return
219
267
  if (/\.page\.(jsx?|tsx?)$/.test(filename)) {
220
- const combined = [...discoverPageFiles(srcDir), ...modulePageFiles]
268
+ const refreshedPageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
221
269
  const regenPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
222
- fs.writeFileSync(regenPath, generatePagesModule(combined, srcDir, regenPath))
223
- writePageHydrateStubs(combined, srcDir, ossyDir)
270
+ fs.writeFileSync(regenPath, generatePagesModule(refreshedPageFiles, srcDir, regenPath))
271
+ writePageHydrateStubs(refreshedPageFiles, srcDir, ossyDir)
224
272
  if (typeof watcher?.invalidate === 'function') {
225
273
  watcher.invalidate(regenPath)
226
- for (const f of combined) {
274
+ for (const f of refreshedPageFiles) {
227
275
  const hid = clientHydrateIdForPage(f, srcDir)
228
276
  watcher.invalidate(path.join(ossyDir, `hydrate-${hid}.jsx`))
229
277
  }
230
278
  }
231
279
  }
232
280
  if (/\.api\.(mjs|cjs|js)$/.test(filename)) {
233
- regenApiBundle()
281
+ regenApiGenerated()
282
+ }
283
+ if (/\.task\.(mjs|cjs|js)$/.test(filename)) {
284
+ regenTasksGenerated()
234
285
  }
235
286
  const norm = filename.replace(/\\/g, '/')
236
287
  if (/\.resource\.js$/.test(norm) && norm.includes('resource-templates/')) {
package/cli/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { build } from './build.js'
3
+ import { dev } from './dev.js'
4
+
5
+ const [,, command, ...restArgs] = process.argv
6
+
7
+ if (!command) {
8
+ console.error(
9
+ '[@ossy/app] No command provided. Usage: app dev | build'
10
+ )
11
+ process.exit(1)
12
+ }
13
+
14
+ const run = async () => {
15
+ if (command === 'dev') {
16
+ await dev(restArgs)
17
+ return
18
+ }
19
+ if (command === 'build') {
20
+ await build(restArgs)
21
+ return
22
+ }
23
+ console.error(`[@ossy/app] Unknown command: ${command}`)
24
+ process.exit(1)
25
+ }
26
+
27
+ run().catch((err) => {
28
+ console.error(err)
29
+ process.exit(1)
30
+ })
package/cli/server.js CHANGED
@@ -8,14 +8,14 @@ import { prerenderToNodeStream } from 'react-dom/static'
8
8
  import { ProxyInternal } from './proxy-internal.js'
9
9
  import cookieParser from 'cookie-parser'
10
10
 
11
- import pages from '%%@ossy/pages/source-file%%'
12
- import ApiRoutes from '%%@ossy/api/source-file%%'
13
- import Middleware from '%%@ossy/middleware/source-file%%'
14
- import configModule from '%%@ossy/config/source-file%%'
11
+ import pages from './.ossy/pages.bundle.js'
12
+ import ApiRoutes from './.ossy/api.bundle.js'
13
+ import Middleware from './middleware.js'
14
+ import configModule from './config.js'
15
15
 
16
16
  const buildTimeConfig = configModule?.default ?? configModule ?? {}
17
17
 
18
- /** `api.generated.js` is always present; default may still be empty. */
18
+ /** API bundle default may be an empty array. */
19
19
  const apiRouteList = ApiRoutes ?? []
20
20
  const pageList = pages ?? []
21
21
 
@@ -1,5 +1,5 @@
1
1
  import 'dotenv/config'
2
- import taskHandlers from '%%@ossy/tasks/source-file%%'
2
+ import taskHandlers from './.ossy/tasks.bundle.js'
3
3
  import { runWorkerScheduler } from './worker-runtime.js'
4
4
 
5
5
  runWorkerScheduler(taskHandlers)
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
7
7
  "type": "module",
8
+ "bin": {
9
+ "app": "./cli/index.js"
10
+ },
8
11
  "scripts": {
9
12
  "build": "echo Building not required",
10
13
  "test": "node --experimental-vm-modules ../node_modules/jest/bin/jest.js --verbose",
@@ -24,14 +27,14 @@
24
27
  "@babel/eslint-parser": "^7.15.8",
25
28
  "@babel/preset-react": "^7.26.3",
26
29
  "@babel/register": "^7.25.9",
27
- "@ossy/connected-components": "^1.0.8",
28
- "@ossy/design-system": "^1.0.8",
29
- "@ossy/pages": "^1.0.8",
30
- "@ossy/router": "^1.0.8",
31
- "@ossy/router-react": "^1.0.8",
32
- "@ossy/sdk": "^1.0.8",
33
- "@ossy/sdk-react": "^1.0.8",
34
- "@ossy/themes": "^1.0.8",
30
+ "@ossy/connected-components": "^1.1.0",
31
+ "@ossy/design-system": "^1.1.0",
32
+ "@ossy/pages": "^1.1.0",
33
+ "@ossy/router": "^1.1.0",
34
+ "@ossy/router-react": "^1.1.0",
35
+ "@ossy/sdk": "^1.1.0",
36
+ "@ossy/sdk-react": "^1.1.0",
37
+ "@ossy/themes": "^1.1.0",
35
38
  "@rollup/plugin-alias": "^6.0.0",
36
39
  "@rollup/plugin-babel": "6.1.0",
37
40
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -64,5 +67,5 @@
64
67
  "README.md",
65
68
  "tsconfig.json"
66
69
  ],
67
- "gitHead": "1a757e16ac932fbf234e8257e2393be1e2a80c60"
70
+ "gitHead": "5977a77f97776131077a848081098ab6ec4cf837"
68
71
  }