@uniweb/build 0.12.0 → 0.13.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/package.json +3 -3
- package/src/dev/plugin.js +2 -1
- package/src/docs.js +2 -1
- package/src/foundation/config.js +11 -5
- package/src/generate-entry.js +11 -4
- package/src/index.js +7 -1
- package/src/schema.js +31 -15
- package/src/site/config.js +14 -3
- package/src/utils/classify-package.js +102 -0
- package/src/utils/foundation-source-root.js +65 -0
- package/src/vite-foundation-plugin.js +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"@uniweb/theming": "0.1.3"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@uniweb/
|
|
61
|
+
"@uniweb/runtime": "0.8.9",
|
|
62
62
|
"@uniweb/schemas": "0.2.1",
|
|
63
|
-
"@uniweb/
|
|
63
|
+
"@uniweb/content-reader": "1.1.9"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/dev/plugin.js
CHANGED
|
@@ -25,6 +25,7 @@ import { watch } from 'node:fs'
|
|
|
25
25
|
import { readFile } from 'node:fs/promises'
|
|
26
26
|
import { existsSync } from 'node:fs'
|
|
27
27
|
import { build } from 'vite'
|
|
28
|
+
import { resolveFoundationSrcPath } from '../utils/foundation-source-root.js'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Create the foundation dev plugin
|
|
@@ -168,7 +169,7 @@ export function foundationDevPlugin(options = {}) {
|
|
|
168
169
|
|
|
169
170
|
// Watch foundation source for changes
|
|
170
171
|
if (shouldWatch) {
|
|
171
|
-
const srcPath =
|
|
172
|
+
const srcPath = resolveFoundationSrcPath(resolvedFoundationPath)
|
|
172
173
|
|
|
173
174
|
// Debounce rebuilds
|
|
174
175
|
let rebuildTimeout = null
|
package/src/docs.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readFile, writeFile } from 'node:fs/promises'
|
|
|
9
9
|
import { existsSync } from 'node:fs'
|
|
10
10
|
import { join, isAbsolute } from 'node:path'
|
|
11
11
|
import { buildSchema } from './schema.js'
|
|
12
|
+
import { resolveFoundationSrcPath } from './utils/foundation-source-root.js'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Generate markdown documentation for a single component
|
|
@@ -174,7 +175,7 @@ export async function generateDocs(foundationDir, options = {}) {
|
|
|
174
175
|
schema = JSON.parse(schemaContent)
|
|
175
176
|
} else {
|
|
176
177
|
// Build schema from source
|
|
177
|
-
const srcDir =
|
|
178
|
+
const srcDir = resolveFoundationSrcPath(foundationDir)
|
|
178
179
|
if (!existsSync(srcDir)) {
|
|
179
180
|
throw new Error(`Source directory not found: ${srcDir}`)
|
|
180
181
|
}
|
package/src/foundation/config.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { resolve } from 'node:path'
|
|
27
27
|
import { foundationPlugin } from '../vite-foundation-plugin.js'
|
|
28
|
+
import { resolveFoundationSrcDir } from '../utils/foundation-source-root.js'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Default externals for foundations
|
|
@@ -61,8 +62,16 @@ const DEFAULT_EXTERNALS = [
|
|
|
61
62
|
* @returns {Promise<Object>} Vite configuration
|
|
62
63
|
*/
|
|
63
64
|
export async function defineFoundationConfig(options = {}) {
|
|
65
|
+
// Determine foundation root (where vite.config.js is)
|
|
66
|
+
const foundationRoot = process.cwd()
|
|
67
|
+
|
|
68
|
+
// Source directory derived from package.json::main. `'src'` for legacy
|
|
69
|
+
// nested layouts, `'.'` for flat layouts. The default `entry` follows.
|
|
70
|
+
const srcDir = resolveFoundationSrcDir(foundationRoot)
|
|
71
|
+
const defaultEntry = srcDir === '.' ? '_entry.generated.js' : `${srcDir}/_entry.generated.js`
|
|
72
|
+
|
|
64
73
|
const {
|
|
65
|
-
entry =
|
|
74
|
+
entry = defaultEntry,
|
|
66
75
|
fileName = 'foundation',
|
|
67
76
|
sections: sectionPaths,
|
|
68
77
|
externals: additionalExternals = [],
|
|
@@ -74,9 +83,6 @@ export async function defineFoundationConfig(options = {}) {
|
|
|
74
83
|
...restOptions
|
|
75
84
|
} = options
|
|
76
85
|
|
|
77
|
-
// Determine foundation root (where vite.config.js is)
|
|
78
|
-
const foundationRoot = process.cwd()
|
|
79
|
-
|
|
80
86
|
// Build externals list
|
|
81
87
|
const externals = includeDefaultExternals
|
|
82
88
|
? [...DEFAULT_EXTERNALS, ...additionalExternals]
|
|
@@ -109,7 +115,7 @@ export async function defineFoundationConfig(options = {}) {
|
|
|
109
115
|
// Build the plugins array
|
|
110
116
|
// foundationPlugin handles entry generation and schema building
|
|
111
117
|
const plugins = [
|
|
112
|
-
foundationPlugin({ srcDir
|
|
118
|
+
foundationPlugin({ srcDir, sections: sectionPaths }),
|
|
113
119
|
tailwind && tailwindcss(),
|
|
114
120
|
react(),
|
|
115
121
|
svgr(),
|
package/src/generate-entry.js
CHANGED
|
@@ -75,17 +75,23 @@ async function detectHostShareableImports(srcDir) {
|
|
|
75
75
|
/**
|
|
76
76
|
* Detect foundation config file (for props, vars, etc.)
|
|
77
77
|
*
|
|
78
|
-
* Looks for:
|
|
78
|
+
* Looks for (in priority order): main.js, main.jsx, foundation.js, foundation.jsx
|
|
79
|
+
*
|
|
80
|
+
* The canonical name is `main.js` (the foundation package's main authored
|
|
81
|
+
* module). `foundation.js` is the legacy name and remains supported so
|
|
82
|
+
* existing foundations build without a rename.
|
|
79
83
|
*
|
|
80
84
|
* The file should export:
|
|
81
85
|
* - props (optional) - Foundation-wide props
|
|
82
86
|
* - vars (optional) - CSS custom properties (also read by schema builder)
|
|
83
87
|
* - defaultLayout (optional) - Default layout name
|
|
84
88
|
*
|
|
85
|
-
* Note: Layout components are now discovered from
|
|
89
|
+
* Note: Layout components are now discovered from layouts/ at the source root.
|
|
86
90
|
*/
|
|
87
91
|
function detectFoundationExports(srcDir) {
|
|
88
92
|
const candidates = [
|
|
93
|
+
{ path: 'main.js', ext: 'js' },
|
|
94
|
+
{ path: 'main.jsx', ext: 'jsx' },
|
|
89
95
|
{ path: 'foundation.js', ext: 'js' },
|
|
90
96
|
{ path: 'foundation.jsx', ext: 'jsx' },
|
|
91
97
|
]
|
|
@@ -374,8 +380,9 @@ export function shouldRegenerateForFile(file, srcDir) {
|
|
|
374
380
|
return 'meta.js changed'
|
|
375
381
|
}
|
|
376
382
|
|
|
377
|
-
// foundation.js / foundation.jsx at root —
|
|
378
|
-
|
|
383
|
+
// main.js / main.jsx / foundation.js / foundation.jsx at root —
|
|
384
|
+
// the authored declarations file affects the capabilities import.
|
|
385
|
+
if (/^(main|foundation)\.(js|jsx)$/.test(rel)) {
|
|
379
386
|
return 'foundation config changed'
|
|
380
387
|
}
|
|
381
388
|
|
package/src/index.js
CHANGED
|
@@ -48,7 +48,13 @@ export {
|
|
|
48
48
|
export { defineFoundationConfig } from './foundation/config.js'
|
|
49
49
|
|
|
50
50
|
// Site config
|
|
51
|
-
export { defineSiteConfig } from './site/config.js'
|
|
51
|
+
export { defineSiteConfig, detectFoundationType } from './site/config.js'
|
|
52
|
+
|
|
53
|
+
// Foundation source root resolution (reads package.json::main)
|
|
54
|
+
export { resolveFoundationSrcDir, resolveFoundationSrcPath } from './utils/foundation-source-root.js'
|
|
55
|
+
|
|
56
|
+
// Package classification (foundation vs site)
|
|
57
|
+
export { classifyPackage, isExtensionPackage } from './utils/classify-package.js'
|
|
52
58
|
|
|
53
59
|
// Default export is the combined Vite plugin
|
|
54
60
|
export { default } from './vite-foundation-plugin.js'
|
package/src/schema.js
CHANGED
|
@@ -19,8 +19,10 @@ import { inferTitle } from './utils/infer-title.js'
|
|
|
19
19
|
// Component meta file name
|
|
20
20
|
const META_FILE_NAME = 'meta.js'
|
|
21
21
|
|
|
22
|
-
// Foundation
|
|
23
|
-
|
|
22
|
+
// Foundation authored-declarations file name. `main.js` is the canonical
|
|
23
|
+
// new name; `foundation.js` is the legacy name kept for backward compat.
|
|
24
|
+
// Whichever exists at the source root is loaded.
|
|
25
|
+
const FOUNDATION_FILE_NAMES = ['main.js', 'foundation.js']
|
|
24
26
|
|
|
25
27
|
// Default paths to scan for section types (relative to srcDir)
|
|
26
28
|
const DEFAULT_SECTION_PATHS = ['sections']
|
|
@@ -62,18 +64,26 @@ export async function loadComponentMeta(componentDir) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
|
-
* Load package.json from foundation root
|
|
66
|
-
* Extracts identity fields: name, version, description
|
|
67
|
+
* Load package.json from a foundation's root.
|
|
68
|
+
* Extracts identity fields: name, version, description.
|
|
67
69
|
*
|
|
68
|
-
*
|
|
70
|
+
* The foundation's `package.json` lives at the *foundation root*, not at
|
|
71
|
+
* the source root. For the legacy nested layout (`main: "./src/_entry.generated.js"`),
|
|
72
|
+
* foundation root is `dirname(srcDir)`. For the flat layout
|
|
73
|
+
* (`main: "./_entry.generated.js"`), `srcDir` IS the foundation root —
|
|
74
|
+
* so we check srcDir first, then fall back to its parent.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} srcDir - Foundation source directory
|
|
69
77
|
* @returns {Object} Identity fields from package.json
|
|
70
78
|
*/
|
|
71
79
|
export async function loadPackageJson(srcDir) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
const candidates = [
|
|
81
|
+
join(srcDir, 'package.json'), // flat layout: srcDir IS the foundation root
|
|
82
|
+
join(dirname(srcDir), 'package.json'), // legacy nested layout
|
|
83
|
+
]
|
|
84
|
+
const packagePath = candidates.find(p => existsSync(p))
|
|
75
85
|
|
|
76
|
-
if (!
|
|
86
|
+
if (!packagePath) {
|
|
77
87
|
return {}
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -94,22 +104,28 @@ export async function loadPackageJson(srcDir) {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
/**
|
|
97
|
-
* Load foundation-level config file (foundation.js)
|
|
107
|
+
* Load foundation-level config file (main.js, fallback foundation.js)
|
|
98
108
|
*
|
|
99
109
|
* Contains foundation-wide configuration:
|
|
100
110
|
* - vars: CSS custom properties sites can override
|
|
101
|
-
* -
|
|
111
|
+
* - defaultLayout: Default layout name
|
|
112
|
+
* - props: Foundation-wide props
|
|
102
113
|
* - Future: providers, middleware, etc.
|
|
103
114
|
*/
|
|
104
115
|
export async function loadFoundationConfig(srcDir) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
let filePath = null
|
|
117
|
+
for (const name of FOUNDATION_FILE_NAMES) {
|
|
118
|
+
const candidate = join(srcDir, name)
|
|
119
|
+
if (existsSync(candidate)) {
|
|
120
|
+
filePath = candidate
|
|
121
|
+
break
|
|
122
|
+
}
|
|
108
123
|
}
|
|
124
|
+
if (!filePath) return {}
|
|
125
|
+
|
|
109
126
|
try {
|
|
110
127
|
const module = await import(pathToFileURL(filePath).href)
|
|
111
128
|
// Support both default export and named exports
|
|
112
|
-
// Note: Layout/layouts no longer read from foundation.js — layouts come from src/layouts/ discovery
|
|
113
129
|
return {
|
|
114
130
|
...module.default,
|
|
115
131
|
vars: module.vars || module.default?.vars,
|
package/src/site/config.js
CHANGED
|
@@ -25,6 +25,7 @@ import { resolve, dirname, join } from 'node:path'
|
|
|
25
25
|
import yaml from 'js-yaml'
|
|
26
26
|
import { generateEntryPoint, shouldRegenerateForFile } from '../generate-entry.js'
|
|
27
27
|
import { importMapPlugin } from '../import-map-plugin.js'
|
|
28
|
+
import { resolveFoundationSrcPath } from '../utils/foundation-source-root.js'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Normalize a base path for Vite compatibility
|
|
@@ -60,7 +61,7 @@ function normalizeBasePath(raw) {
|
|
|
60
61
|
* @param {string|Object} foundation - Foundation config from site.yml
|
|
61
62
|
* @returns {{ type: 'local'|'npm'|'url', name?: string, url?: string, cssUrl?: string, path?: string }}
|
|
62
63
|
*/
|
|
63
|
-
function detectFoundationType(foundation, siteRoot) {
|
|
64
|
+
export function detectFoundationType(foundation, siteRoot) {
|
|
64
65
|
// Object form with explicit URL
|
|
65
66
|
if (foundation && typeof foundation === 'object') {
|
|
66
67
|
if (foundation.url) {
|
|
@@ -228,6 +229,16 @@ export async function defineSiteConfig(options = {}) {
|
|
|
228
229
|
// Read site.yml
|
|
229
230
|
const siteConfig = readSiteConfig(siteRoot)
|
|
230
231
|
|
|
232
|
+
// Allow callers to override `foundation:` without modifying site.yml on
|
|
233
|
+
// disk. Used by `uniweb deploy` to substitute a workspace-local file: ref
|
|
234
|
+
// with the resolved registry ref (`@ns/name@ver`) for the duration of the
|
|
235
|
+
// deploy build, so the site builds in runtime/link mode against the just-
|
|
236
|
+
// published artifact instead of bundling the local source.
|
|
237
|
+
const foundationOverride = process.env.UNIWEB_FOUNDATION_REF
|
|
238
|
+
if (foundationOverride) {
|
|
239
|
+
siteConfig.foundation = foundationOverride
|
|
240
|
+
}
|
|
241
|
+
|
|
231
242
|
// Determine base path for deployment (priority: option > env > site.yml)
|
|
232
243
|
// Normalize: ensure leading slash, collapse repeated slashes, add trailing slash for Vite
|
|
233
244
|
const rawBase = baseOption || process.env.UNIWEB_BASE || siteConfig.base
|
|
@@ -283,7 +294,7 @@ export async function defineSiteConfig(options = {}) {
|
|
|
283
294
|
const ensureFoundationEntryPlugin = !isRuntimeMode && foundationInfo.type === 'local' ? {
|
|
284
295
|
name: 'uniweb:ensure-foundation-entry',
|
|
285
296
|
async config() {
|
|
286
|
-
const srcDir =
|
|
297
|
+
const srcDir = resolveFoundationSrcPath(foundationInfo.path)
|
|
287
298
|
const entryPath = join(srcDir, '_entry.generated.js')
|
|
288
299
|
|
|
289
300
|
// Always regenerate on dev start to ensure it's current
|
|
@@ -300,7 +311,7 @@ export async function defineSiteConfig(options = {}) {
|
|
|
300
311
|
|
|
301
312
|
configureServer(server) {
|
|
302
313
|
// Watch foundation src for structural changes that affect the entry
|
|
303
|
-
const srcDir =
|
|
314
|
+
const srcDir = resolveFoundationSrcPath(foundationInfo.path)
|
|
304
315
|
const entryPath = join(srcDir, '_entry.generated.js')
|
|
305
316
|
|
|
306
317
|
server.watcher.add(srcDir)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a Uniweb package as `'foundation'`, `'site'`, or `null`.
|
|
3
|
+
*
|
|
4
|
+
* One sync classifier shared by the build, the CLI, and any tooling that
|
|
5
|
+
* needs to ask "what kind of Uniweb package is this directory?". Replaces
|
|
6
|
+
* four duplicated implementations across the CLI that used different
|
|
7
|
+
* signals and could disagree at the edges.
|
|
8
|
+
*
|
|
9
|
+
* Signals are checked strict-first, lenient-fallback:
|
|
10
|
+
*
|
|
11
|
+
* 1. `package.json::main` matches `_entry.generated.js` → foundation
|
|
12
|
+
* (mandatory by build contract; uniquely identifies a Uniweb foundation;
|
|
13
|
+
* set at scaffold time, before any install or build)
|
|
14
|
+
* 2. `site.yml` or `document.yml` at the package root → site
|
|
15
|
+
* 3. Authored declarations file (`main.js`, fallback `foundation.js`) at
|
|
16
|
+
* the resolved source root → foundation
|
|
17
|
+
* (covers unscaffolded directories without `package.json` yet)
|
|
18
|
+
* 4. `pages/` at the package root → site
|
|
19
|
+
* 5. otherwise → null
|
|
20
|
+
*
|
|
21
|
+
* Sync because the I/O is microsecond-scale and consumers were already
|
|
22
|
+
* blocking on the async version. Sync also lets workspace-scan helpers
|
|
23
|
+
* (findFoundations, findSites) be plain `.filter()` calls.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
27
|
+
import { join } from 'node:path'
|
|
28
|
+
import { resolveFoundationSrcPath } from './foundation-source-root.js'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} packagePath - Absolute path to the package directory.
|
|
32
|
+
* @returns {'foundation'|'site'|null}
|
|
33
|
+
*/
|
|
34
|
+
export function classifyPackage(packagePath) {
|
|
35
|
+
// 1. Strongest foundation marker: package.json::main points at the
|
|
36
|
+
// build-generated entry. Only Uniweb foundations have this shape.
|
|
37
|
+
const pkgPath = join(packagePath, 'package.json')
|
|
38
|
+
if (existsSync(pkgPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
41
|
+
if (typeof pkg.main === 'string' && /_entry\.generated\.js$/.test(pkg.main)) {
|
|
42
|
+
return 'foundation'
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Malformed package.json — fall through to file-based signals.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. Strongest site marker: configuration file at root.
|
|
50
|
+
if (existsSync(join(packagePath, 'site.yml')) ||
|
|
51
|
+
existsSync(join(packagePath, 'document.yml'))) {
|
|
52
|
+
return 'site'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3. Foundation fallback for unscaffolded directories: authored
|
|
56
|
+
// declarations file at the resolved source root. Accept both the
|
|
57
|
+
// new name (main.js) and the legacy name (foundation.js).
|
|
58
|
+
const srcDir = resolveFoundationSrcPath(packagePath)
|
|
59
|
+
if (existsSync(join(srcDir, 'main.js'))) return 'foundation'
|
|
60
|
+
if (existsSync(join(srcDir, 'foundation.js'))) return 'foundation'
|
|
61
|
+
|
|
62
|
+
// 4. Site fallback: pages/ at root.
|
|
63
|
+
if (existsSync(join(packagePath, 'pages'))) return 'site'
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check whether a foundation package declares `extension: true` in its
|
|
70
|
+
* authored declarations file. Uses a regex on the source rather than
|
|
71
|
+
* importing the module — keeps the classifier sync and side-effect-free,
|
|
72
|
+
* and works before any install.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} packagePath - Absolute path to the package directory.
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
export function isExtensionPackage(packagePath) {
|
|
78
|
+
const srcDir = resolveFoundationSrcPath(packagePath)
|
|
79
|
+
// Prefer the schema's recorded role if the build has run.
|
|
80
|
+
const schemaPath = join(packagePath, 'dist', 'meta', 'schema.json')
|
|
81
|
+
if (existsSync(schemaPath)) {
|
|
82
|
+
try {
|
|
83
|
+
const schema = JSON.parse(readFileSync(schemaPath, 'utf8'))
|
|
84
|
+
if (schema?._self?.role === 'extension') return true
|
|
85
|
+
} catch {
|
|
86
|
+
// Fall through to source-based check.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Source-based fallback. Accept both filenames.
|
|
90
|
+
for (const name of ['main.js', 'foundation.js']) {
|
|
91
|
+
const filePath = join(srcDir, name)
|
|
92
|
+
if (existsSync(filePath)) {
|
|
93
|
+
try {
|
|
94
|
+
const content = readFileSync(filePath, 'utf8')
|
|
95
|
+
return /extension\s*:\s*true/.test(content)
|
|
96
|
+
} catch {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a foundation package's source root by reading its `package.json::main`.
|
|
3
|
+
*
|
|
4
|
+
* Two layouts are supported:
|
|
5
|
+
*
|
|
6
|
+
* - Nested (legacy): `main: "./src/_entry.generated.js"` → source root is `<foundationDir>/src/`
|
|
7
|
+
* - Flat: `main: "./_entry.generated.js"` → source root is `<foundationDir>/`
|
|
8
|
+
*
|
|
9
|
+
* The build derives every other path (sections, components, layouts, foundation.js,
|
|
10
|
+
* styles.css, the generated entry) from this single value. There is no filesystem
|
|
11
|
+
* probing — `main` is the source of truth for where the package's code lives,
|
|
12
|
+
* which is also exactly what npm-style consumers use.
|
|
13
|
+
*
|
|
14
|
+
* Existing foundations in the wild keep `main: "./src/_entry.generated.js"` and
|
|
15
|
+
* resolve to the same nested layout they always had. New scaffolds (per the
|
|
16
|
+
* first-run-ux plan, Thread D) ship with the flat layout and `main: "./_entry.generated.js"`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
20
|
+
import { dirname, join, resolve } from 'node:path'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a foundation's source directory (relative to the foundation root).
|
|
24
|
+
*
|
|
25
|
+
* @param {string} foundationDir - Absolute path to the foundation package root.
|
|
26
|
+
* @returns {string} Source directory, relative to `foundationDir` (e.g. `'src'` or `'.'`).
|
|
27
|
+
*/
|
|
28
|
+
export function resolveFoundationSrcDir(foundationDir) {
|
|
29
|
+
const pkgPath = join(foundationDir, 'package.json')
|
|
30
|
+
if (!existsSync(pkgPath)) {
|
|
31
|
+
// No package.json — fall back to legacy nested layout. Don't throw; the
|
|
32
|
+
// caller (e.g. the docs builder) may be inspecting a partial directory
|
|
33
|
+
// and existsSync checks downstream will surface the real error.
|
|
34
|
+
return 'src'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let pkg
|
|
38
|
+
try {
|
|
39
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
40
|
+
} catch {
|
|
41
|
+
return 'src'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const main = pkg.main
|
|
45
|
+
if (typeof main !== 'string' || !main) {
|
|
46
|
+
return 'src'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// dirname('./src/_entry.generated.js') → './src' → 'src'
|
|
50
|
+
// dirname('./_entry.generated.js') → '.' → '.'
|
|
51
|
+
// dirname('src/_entry.generated.js') → 'src' → 'src'
|
|
52
|
+
const dir = dirname(main).replace(/^\.\//, '')
|
|
53
|
+
return dir || '.'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a foundation's absolute source directory.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} foundationDir - Absolute path to the foundation package root.
|
|
60
|
+
* @returns {string} Absolute path to the source directory.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveFoundationSrcPath(foundationDir) {
|
|
63
|
+
const srcDir = resolveFoundationSrcDir(foundationDir)
|
|
64
|
+
return srcDir === '.' ? foundationDir : resolve(foundationDir, srcDir)
|
|
65
|
+
}
|
|
@@ -134,8 +134,10 @@ async function emitRuntimePin(outDir, projectRoot) {
|
|
|
134
134
|
if (!runtimeVersion) return
|
|
135
135
|
|
|
136
136
|
// Read foundation's own package.json for an optional runtimePolicy
|
|
137
|
-
// field. Default policy (auto-
|
|
138
|
-
// only record the foundation's override
|
|
137
|
+
// field. Default policy (auto-minor) is applied platform-side when
|
|
138
|
+
// the field is omitted; we only record the foundation's override
|
|
139
|
+
// here if explicitly set. See framework/docs/reference/foundation-config.md
|
|
140
|
+
// for the full set of `uniweb.*` fields foundations can declare.
|
|
139
141
|
let policy = null
|
|
140
142
|
try {
|
|
141
143
|
const foundationPkgPath = join(projectRoot, 'package.json')
|