@mosetta/build 0.0.0-stage → 0.0.9
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/CHANGELOG.md +144 -0
- package/LICENSE +21 -0
- package/README.md +14 -2
- package/package.json +34 -3
- package/src/archive.mjs +140 -0
- package/src/artifactCache.mjs +78 -0
- package/src/browserField.mjs +90 -0
- package/src/build.mjs +576 -0
- package/src/buildConfig.mjs +22 -0
- package/src/buildDependencies.mjs +97 -0
- package/src/buildModules.mjs +328 -0
- package/src/buildServer.mjs +155 -0
- package/src/cjsExports.mjs +81 -0
- package/src/conditions.mjs +21 -0
- package/src/configBundle.mjs +59 -0
- package/src/css.mjs +108 -0
- package/src/discovery.mjs +184 -0
- package/src/fileSystem.mjs +69 -0
- package/src/format.mjs +4 -0
- package/src/hash.mjs +13 -0
- package/src/memoryFileSystem.mjs +81 -0
- package/src/moduleName.mjs +11 -0
- package/src/nodeApi.mjs +22 -0
- package/src/packageMeta.mjs +37 -0
- package/src/packagesResolver.mjs +159 -0
- package/src/plugins/assets.mjs +41 -0
- package/src/plugins/cssIntercept.mjs +38 -0
- package/src/plugins/defineEnv.mjs +14 -0
- package/src/plugins/monolithCompat.mjs +84 -0
- package/src/plugins/portResolve.mjs +80 -0
- package/src/plugins/portResolveEsbuild.mjs +103 -0
- package/src/plugins/transform.mjs +56 -0
- package/src/probeFile.mjs +65 -0
- package/src/release.mjs +516 -0
- package/src/scanner.mjs +397 -0
- package/src/settings.mjs +39 -0
- package/src/sourcemap.mjs +26 -0
- package/src/specifiers.mjs +76 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @mosetta/build
|
|
2
|
+
|
|
3
|
+
## 0.0.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @mosetta/runtime@0.0.9
|
|
9
|
+
- @mosetta/server@0.0.9
|
|
10
|
+
- @mosetta/server-action@0.0.9
|
|
11
|
+
- @mosetta/router@0.0.9
|
|
12
|
+
- @mosetta/tar@0.0.9
|
|
13
|
+
|
|
14
|
+
## 0.0.8
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- A project's own tile palette. The registry keeps the palettes a project's site takes tiles from;
|
|
19
|
+
the factory reads each one's tile host after the platform's, and takes a tile of a host other than
|
|
20
|
+
the primary only with its integrity. The rule a site's tile hosts merge by lives in
|
|
21
|
+
`@mosetta/core` (`mergeTileHosts`), so the editor offers the blocks of every tile host of the site
|
|
22
|
+
by the same rule (`TILE_HOSTS_TOKEN` in `@mosetta/server-action`). The platform's API publishes
|
|
23
|
+
into `palettes/<brand>` for the members of the project of that namespace, with the palette's stores
|
|
24
|
+
granted to that one request. `mosetta build --released <descriptor>` leaves out what a platform
|
|
25
|
+
already holds; `checkPaletteBuild` says what a palette may hold. The CLI's and the build's output
|
|
26
|
+
speaks the vocabulary: tile palettes, tiles, package tiles.
|
|
27
|
+
- Updated dependencies
|
|
28
|
+
- @mosetta/server@0.0.8
|
|
29
|
+
- @mosetta/server-action@0.0.8
|
|
30
|
+
- @mosetta/runtime@0.0.8
|
|
31
|
+
- @mosetta/router@0.0.8
|
|
32
|
+
- @mosetta/tar@0.0.8
|
|
33
|
+
|
|
34
|
+
## 0.0.7
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- One vocabulary: code is tiles, content is blueprints, running requests is the factory, and a tile and a
|
|
39
|
+
blueprint made in the factory give a mosaic. A tile is a folder under `tiles/` (was `modules/`) whose
|
|
40
|
+
files are its sides, each defined with `define<Side>Side` — `defineComponentSide`, `defineEditorSide`,
|
|
41
|
+
`defineFallbackSide`, `defineRootComponentSide`, `defineServerSide`, `defineSsrSide` (were
|
|
42
|
+
`define…Module`); `useTile` (was `useModule`), `markInternalTile`, `BlueprintRoot`,
|
|
43
|
+
`BlueprintNodeRenderer`. `mosetta.json` names the tile `brand` (was `namespace`; a config that still
|
|
44
|
+
says `namespace` is refused with the word to use). The store connectors' package is `@mosetta/stores`
|
|
45
|
+
(was `@mosetta/provider`): `IStoreConnector`, `StoreConnectors`, `tileHostsOf`. The server's runner
|
|
46
|
+
is the factory gate (`@mosetta/server/factoryGate`, was `/runner`). Nothing on the wire moves: tile
|
|
47
|
+
ids keep `_ns_`, a build keeps `modules/<id>/`, stored JSON keeps its fields.
|
|
48
|
+
- Updated dependencies
|
|
49
|
+
- @mosetta/server-action@0.0.7
|
|
50
|
+
- @mosetta/runtime@0.0.7
|
|
51
|
+
- @mosetta/server@0.0.7
|
|
52
|
+
- @mosetta/router@0.0.7
|
|
53
|
+
- @mosetta/tar@0.0.7
|
|
54
|
+
|
|
55
|
+
## 0.0.6
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- A site's code may come from several sources — the host's stores, a project's own distributions, another vendor's. A
|
|
60
|
+
store of the registry names its source (`source`; none — the host's), and the runner reads every source's public and
|
|
61
|
+
private catalogs into one: a namespace belongs to the first source holding it, a package to the first that has it
|
|
62
|
+
(other bytes under its id are said, the first stands). Every entity is read from the source its catalog entry names —
|
|
63
|
+
blocks, packages and singletons alike, by the runner, the site's renderer and the browser: the page envelope carries a
|
|
64
|
+
`providers` table (key → base) and the catalog slice the key of each entity. The page no longer says where its code
|
|
65
|
+
lives: the node's `provider` is gone. A server module runs only when a private catalog of the site names it; a site
|
|
66
|
+
whose catalogs were never read runs none (503). A build into a target leaves what the target already holds: released
|
|
67
|
+
library roots and published packages are not built again.
|
|
68
|
+
- Updated dependencies
|
|
69
|
+
- @mosetta/server@0.0.6
|
|
70
|
+
- @mosetta/runtime@0.0.6
|
|
71
|
+
- @mosetta/router@0.0.6
|
|
72
|
+
- @mosetta/server-action@0.0.6
|
|
73
|
+
- @mosetta/tar@0.0.6
|
|
74
|
+
|
|
75
|
+
## 0.0.5
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- 92417dc: Who may call a server action is the action's to say: every action has an `authorize` (abstract — an action without it
|
|
80
|
+
does not compile), run after the input is validated and before the action; refused, the call is a 401 when nobody is
|
|
81
|
+
vouched for and a 403 otherwise. It decides by the input, the request and the caller — what the host vouches for, in
|
|
82
|
+
the host's own words: the framework passes the identity header's JSON object through whole and knows no roles. The
|
|
83
|
+
mosetta platform says `{ via, login, email?, roles? }` (`roles: ['editor']` for the site's editors, on server actions);
|
|
84
|
+
`mosetta dev` vouches for the developer at the machine as an editor. `<name>.public.ts` and the runner's check of it
|
|
85
|
+
are gone: the platform's gate no longer decides for actions, and only keeps a visitor's calls within bounds.
|
|
86
|
+
- Updated dependencies [92417dc]
|
|
87
|
+
- @mosetta/server-action@0.0.5
|
|
88
|
+
- @mosetta/server@0.0.5
|
|
89
|
+
- @mosetta/router@0.0.5
|
|
90
|
+
- @mosetta/runtime@0.0.5
|
|
91
|
+
- @mosetta/tar@0.0.5
|
|
92
|
+
|
|
93
|
+
## 0.0.4
|
|
94
|
+
|
|
95
|
+
### Patch Changes
|
|
96
|
+
|
|
97
|
+
- 7577aa3: A server module named `<name>.public.ts` answers visitors of the site, not only its editors: it is built, named and
|
|
98
|
+
called like any server module, and its private descriptor says `public`. A host lets a non-editor's call on as a
|
|
99
|
+
visitor's — with no identity, limited per site, module and address, a form's size at most — and the runner answers it
|
|
100
|
+
from a public module only. Opening or closing a module under a version already released is refused.
|
|
101
|
+
- Updated dependencies [7577aa3]
|
|
102
|
+
- Updated dependencies [7577aa3]
|
|
103
|
+
- @mosetta/server-action@0.0.4
|
|
104
|
+
- @mosetta/server@0.0.4
|
|
105
|
+
- @mosetta/router@0.0.4
|
|
106
|
+
- @mosetta/runtime@0.0.4
|
|
107
|
+
- @mosetta/tar@0.0.4
|
|
108
|
+
|
|
109
|
+
## 0.0.3
|
|
110
|
+
|
|
111
|
+
### Patch Changes
|
|
112
|
+
|
|
113
|
+
- 1330d5d: Code is published THROUGH a platform: `mosetta push` describes the build (both catalogs, every file with its SHA-256,
|
|
114
|
+
the commit it comes from), asks the platform's API for a plan and sends the files it asks for — no store key on the
|
|
115
|
+
publisher's side. The release rules check a fresh module's bytes against the catalog entry that vouches for them, on
|
|
116
|
+
either path. A runner can grant stores to server actions only (found by name), never read for its own modules — how
|
|
117
|
+
the platform's API writes code stores whose keys stay with its host; the host names the publish target in a header of
|
|
118
|
+
its own, beside the caller's identity.
|
|
119
|
+
- 886c43a: The release rules live in `@mosetta/build/release`, portable, for every publisher: `mosetta deploy` runs them against a
|
|
120
|
+
provider registry, a platform can run them on its side. A release writes each catalog over exactly the copy it planned
|
|
121
|
+
from and a module file only where none is, so two deploys into one store at once can no longer drop each other's
|
|
122
|
+
entries — the one overtaken plans again. A provider write takes a condition (`ifMatch`, `ifNoneMatch: '*'`), answered
|
|
123
|
+
412 when someone else got there first.
|
|
124
|
+
- Updated dependencies [1330d5d]
|
|
125
|
+
- Updated dependencies [842344f]
|
|
126
|
+
- @mosetta/server@0.0.3
|
|
127
|
+
- @mosetta/server-action@0.0.3
|
|
128
|
+
- @mosetta/router@0.0.3
|
|
129
|
+
- @mosetta/runtime@0.0.3
|
|
130
|
+
- @mosetta/tar@0.0.3
|
|
131
|
+
|
|
132
|
+
## 0.0.2
|
|
133
|
+
|
|
134
|
+
### Patch Changes
|
|
135
|
+
|
|
136
|
+
- The first versioned release. A deployed version never changes, and the stores keep every version
|
|
137
|
+
deployed. A page runs one copy of each framework package — the newest its blocks were built with,
|
|
138
|
+
or the version its root pins; the Framework distribution's updater lifts a page to its framework.
|
|
139
|
+
- Updated dependencies
|
|
140
|
+
- @mosetta/server@0.0.2
|
|
141
|
+
- @mosetta/router@0.0.2
|
|
142
|
+
- @mosetta/runtime@0.0.2
|
|
143
|
+
- @mosetta/server-action@0.0.2
|
|
144
|
+
- @mosetta/tar@0.0.2
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mosetta contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mosetta/build
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The core of the tile forge: builds a tile palette — its tile side artifacts, their dependency
|
|
4
|
+
closure, the tile host descriptor (`modules.json`) — and holds the release rules every publisher
|
|
5
|
+
runs. Environment-agnostic: the file system and the bundling engine are supplied from outside, so
|
|
6
|
+
the same build runs in node (`@mosetta/cli`), in CI or in a platform's build container.
|
|
7
|
+
|
|
8
|
+
## Entry points
|
|
9
|
+
|
|
10
|
+
| Entry | What it holds |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `@mosetta/build` | `build(root, options)` |
|
|
13
|
+
| `@mosetta/build/release` | the release rules: a released version never changes, descriptors only grow, a descriptor is written over exactly the copy the plan was made from; `planRelease`, `release`, the push manifest, `checkPaletteBuild` |
|
|
14
|
+
| `@mosetta/build/archive` | a build from a packed archive of sources, lock and packages |
|
|
15
|
+
| `@mosetta/build/fileSystem`, `./scanner`, `./packagesResolver`, `./artifactCache` | the ports and helpers a host builds with |
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mosetta/build",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "The core of the tile forge: builds a tile palette — its tile side artifacts, their dependency closure, the tile host descriptor. Environment-agnostic — the file system and the bundling engine are supplied from outside.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/build.mjs",
|
|
9
|
+
"./artifactCache": "./src/artifactCache.mjs",
|
|
10
|
+
"./fileSystem": "./src/fileSystem.mjs",
|
|
11
|
+
"./packagesResolver": "./src/packagesResolver.mjs",
|
|
12
|
+
"./scanner": "./src/scanner.mjs",
|
|
13
|
+
"./archive": "./src/archive.mjs",
|
|
14
|
+
"./release": "./src/release.mjs"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@pnpm/dependency-path": "^1000.0.9",
|
|
18
|
+
"@rollup/plugin-commonjs": "^28.0.9",
|
|
19
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
20
|
+
"cjs-module-lexer": "^2.2.0",
|
|
21
|
+
"postcss": "^8.5.15",
|
|
22
|
+
"postcss-value-parser": "^4.2.0",
|
|
23
|
+
"react": "*",
|
|
24
|
+
"react-dom": "*",
|
|
25
|
+
"resolve.exports": "^2.0.3",
|
|
26
|
+
"scheduler": "*",
|
|
27
|
+
"yaml": "^2.9.0",
|
|
28
|
+
"@mosetta/runtime": "0.0.9",
|
|
29
|
+
"@mosetta/server-action": "0.0.9",
|
|
30
|
+
"@mosetta/router": "0.0.9",
|
|
31
|
+
"@mosetta/tar": "0.0.9",
|
|
32
|
+
"@mosetta/server": "0.0.9"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
6
37
|
}
|
package/src/archive.mjs
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { path } from './nodeApi.mjs';
|
|
2
|
+
import { parseTar, gunzip } from '@mosetta/tar';
|
|
3
|
+
import { MemoryFileSystem } from './memoryFileSystem.mjs';
|
|
4
|
+
import { PackagesResolver } from './packagesResolver.mjs';
|
|
5
|
+
import { scanCatalog, scanToolingCatalog, BUILD_CONFIG_FILE } from './scanner.mjs';
|
|
6
|
+
import { build, INERT_BUILD_CONFIG } from './build.mjs';
|
|
7
|
+
import { bundleBuildConfig } from './configBundle.mjs';
|
|
8
|
+
|
|
9
|
+
export { CONFIG_MODULE, bundleBuildConfig, buildConfigFrom } from './configBundle.mjs';
|
|
10
|
+
export { BUILD_CONFIG_FILE } from './scanner.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The build-action body — ONE BINARY tar container, no text encoding:
|
|
14
|
+
*
|
|
15
|
+
* scan.json — manifest: { root, frameworkDir, framework, mode, unpack: [{entry,dir,strip}] }
|
|
16
|
+
* sources.tgz — distributive sources (+ its lock) — the form GitHub serves them in
|
|
17
|
+
* packages/*.tgz — archives of the closure's packages — the form the registry serves them in
|
|
18
|
+
* meta.tgz — package.json/locks of the walk-up contexts (ancestors of the root)
|
|
19
|
+
*
|
|
20
|
+
* The outer container is NOT compressed (its entries are already gzip) — the platform
|
|
21
|
+
* concatenates ready-made archives without repacking anything. Each entry unpacks into the
|
|
22
|
+
* memory FS at its `dir` (virtual paths mirror the layout relative to the lock — the graph's
|
|
23
|
+
* relative paths match the disk, so the artifacts are byte-identical). `strip` cuts the top
|
|
24
|
+
* name segments (a GitHub archive's `<repo>-<sha>/` folder, an npm package's `package/`).
|
|
25
|
+
*
|
|
26
|
+
* From there it is exactly the CLI pipeline: scan (lock + sources, already INSIDE the isolate) →
|
|
27
|
+
* catalog-backed resolver → build(). `mosetta.build.mjs` is NOT executed here by construction
|
|
28
|
+
* (executing a foreign file is an environment capability): the config is always INERT, and a
|
|
29
|
+
* distributive with node plugins is built the traditional way.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export { MemoryFileSystem } from './memoryFileSystem.mjs';
|
|
33
|
+
export { parseTar, writeTar, gzip, gunzip } from '@mosetta/tar';
|
|
34
|
+
|
|
35
|
+
export const SCAN_ENTRY = 'scan.json';
|
|
36
|
+
|
|
37
|
+
const stripSegments = (name, n) => name.split('/').slice(n).join('/');
|
|
38
|
+
|
|
39
|
+
/** Unpack the container: { manifest, fileSystem } — a memory FS with the build's virtual world. */
|
|
40
|
+
export async function unpackArchive(bytes) {
|
|
41
|
+
const outer = parseTar(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes));
|
|
42
|
+
const byName = new Map(outer.map((e) => [e.name, e]));
|
|
43
|
+
const scanEntry = byName.get(SCAN_ENTRY);
|
|
44
|
+
if (!scanEntry) throw new Error(`container has no ${SCAN_ENTRY} — this is not a build-action body`);
|
|
45
|
+
const manifest = JSON.parse(new TextDecoder().decode(scanEntry.data));
|
|
46
|
+
const fileSystem = new MemoryFileSystem();
|
|
47
|
+
for (const u of manifest.unpack ?? []) {
|
|
48
|
+
const entry = byName.get(u.entry);
|
|
49
|
+
if (!entry) throw new Error(`manifest declares ${u.entry}, but the container has no such entry`);
|
|
50
|
+
for (const f of parseTar(await gunzip(entry.data))) {
|
|
51
|
+
const rel = stripSegments(f.name, u.strip ?? 0);
|
|
52
|
+
if (rel) fileSystem.set(path.join(u.dir, rel), f.data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { manifest, fileSystem };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* RECOMPOSITION prep, the framework half: if the distributive has a build config — bundle it
|
|
60
|
+
* into a module and collect its wasm assets, so the caller (the action) only has to pack this
|
|
61
|
+
* into the response to the runner. The config is NOT executed here.
|
|
62
|
+
*
|
|
63
|
+
* The config's closure is the TOOLING one (`scanToolingCatalog`): devDependencies are allowed,
|
|
64
|
+
* because build tooling is exactly what they contain; the product catalog does not see it.
|
|
65
|
+
* Returns `{ code, wasm: [{ name, bytes }] }`, or null when there is no config.
|
|
66
|
+
*/
|
|
67
|
+
export async function prepareConfigRecompose({ manifest, fileSystem, engine }) {
|
|
68
|
+
if (!fileSystem.exists(path.join(manifest.root, BUILD_CONFIG_FILE))) return null;
|
|
69
|
+
const resolver = new PackagesResolver({
|
|
70
|
+
fileSystem,
|
|
71
|
+
catalog: {
|
|
72
|
+
...scanToolingCatalog({ fileSystem, root: manifest.root, frameworkDir: manifest.frameworkDir }),
|
|
73
|
+
framework: manifest.framework ?? {},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const code = await bundleBuildConfig(fileSystem, resolver, {
|
|
77
|
+
engine, root: manifest.root, frameworkDir: manifest.frameworkDir,
|
|
78
|
+
});
|
|
79
|
+
// the config's wasm is declared as DATA (`buildConfigWasm` in mosetta.json): phase 1 does not
|
|
80
|
+
// execute the config and has no other way to learn its needs
|
|
81
|
+
const wasm = [];
|
|
82
|
+
let cfg = null;
|
|
83
|
+
try {
|
|
84
|
+
cfg = fileSystem.readJson(path.join(manifest.root, 'mosetta.json'));
|
|
85
|
+
} catch { /* no mosetta.json — no knob either */ }
|
|
86
|
+
for (const [name, specifier] of Object.entries(cfg?.buildConfigWasm ?? {})) {
|
|
87
|
+
wasm.push({ name, bytes: fileSystem.readFile(resolver.resolve(specifier, manifest.root, null)) });
|
|
88
|
+
}
|
|
89
|
+
return { code, wasm };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The action's full cycle: container → memory FS → scan → build(). Returns build()'s result.
|
|
94
|
+
*
|
|
95
|
+
* `released` — the ids the target's catalog already holds: released libraries and published
|
|
96
|
+
* packages there are not built again (`build`).
|
|
97
|
+
*
|
|
98
|
+
* `evaluate` — the host's way to run a module the build bundled (a block's settings schema); absent
|
|
99
|
+
* (an isolate), the schemas are skipped with a line in the log, as in any build without it.
|
|
100
|
+
*
|
|
101
|
+
* `buildConfig` — the tenant's hooks, ALREADY made executable through recomposition (the config
|
|
102
|
+
* module was added by the runner into the isolate's bundle, the action imported it and passed it
|
|
103
|
+
* here). If the config EXISTS in the sources but no hooks were passed, a build without them
|
|
104
|
+
* would produce artifacts different from local ones, so the refusal is LOUD: the caller must
|
|
105
|
+
* either go through recomposition or build the traditional way.
|
|
106
|
+
*/
|
|
107
|
+
export async function buildFromArchive(bytes, options) {
|
|
108
|
+
return buildUnpacked(await unpackArchive(bytes), options);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The same build over a container already unpacked (`unpackArchive`) — a host that looked inside first does not unpack twice. */
|
|
112
|
+
export async function buildUnpacked({ manifest, fileSystem }, { engine, log = () => {}, buildConfig = null, evaluate = undefined, released = null }) {
|
|
113
|
+
if (!buildConfig && fileSystem.exists(path.join(manifest.root, BUILD_CONFIG_FILE))) {
|
|
114
|
+
throw new Error('the tile palette uses mosetta.build.mjs (build hooks), but no executable hooks '
|
|
115
|
+
+ 'were passed: go through recomposition or build the traditional way (CLI)');
|
|
116
|
+
}
|
|
117
|
+
const mode = manifest.mode === 'production' ? 'production' : 'development';
|
|
118
|
+
const resolver = new PackagesResolver({
|
|
119
|
+
fileSystem,
|
|
120
|
+
catalog: {
|
|
121
|
+
...scanCatalog({
|
|
122
|
+
fileSystem,
|
|
123
|
+
root: manifest.root,
|
|
124
|
+
frameworkDir: manifest.frameworkDir,
|
|
125
|
+
includeDev: mode !== 'production',
|
|
126
|
+
log,
|
|
127
|
+
}),
|
|
128
|
+
framework: manifest.framework ?? {},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
return build(manifest.root, {
|
|
132
|
+
mode, log, fileSystem, resolver, engine,
|
|
133
|
+
frameworkDir: manifest.frameworkDir,
|
|
134
|
+
buildConfig: buildConfig ?? INERT_BUILD_CONFIG,
|
|
135
|
+
// a host that may run the code it built (a container, node) describes the blocks' settings
|
|
136
|
+
...(evaluate ? { evaluate } : {}),
|
|
137
|
+
// what the target already holds is not built again
|
|
138
|
+
...(released ? { released } : {}),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact cache ACROSS build iterations (dev server).
|
|
3
|
+
*
|
|
4
|
+
* The artifact model could not fit this better: every artifact is built by its own graph, is
|
|
5
|
+
* self-contained and links at runtime by name — so editing one file must rebuild only the
|
|
6
|
+
* artifacts whose graphs include it, not all 44. Before the cache, every save in dev rebuilt
|
|
7
|
+
* EVERYTHING, including dependency artifacts whose sources live in node_modules and could not
|
|
8
|
+
* have changed.
|
|
9
|
+
*
|
|
10
|
+
* The validity key is the mtime of the artifact's source graph (`watchFiles`, collected by the
|
|
11
|
+
* port plugins). Such a key behaves correctly in a monorepo by itself — both ours and a
|
|
12
|
+
* consumer's: graph paths are dereferenced (`realPath`), so a workspace package is seen as the
|
|
13
|
+
* real file (`packages/react/src/...`), not a symlink inside node_modules, and editing it
|
|
14
|
+
* honestly invalidates everyone who pulled it in. A missing file yields `-1` — also a
|
|
15
|
+
* divergence, also a rebuild.
|
|
16
|
+
*
|
|
17
|
+
* What graph mtime does NOT catch is whatever changes the build as a whole rather than through
|
|
18
|
+
* files: mode, `mosetta.build.mjs` hooks, the package catalog (a reinstall/`pnpm add` changes
|
|
19
|
+
* resolution while sources do not) and the composition inputs (`mosetta.json`, the roots'
|
|
20
|
+
* `package.json`, `config.json`). That is the "epoch": changing it wipes the cache entirely.
|
|
21
|
+
* Cheaper and more reliable than weaving all of that into every key.
|
|
22
|
+
*
|
|
23
|
+
* The cache is SUPPLIED FROM OUTSIDE (the build has no environment defaults) — the long-lived
|
|
24
|
+
* dev server creates it. A one-shot prod build passes no cache: every build there is the first
|
|
25
|
+
* and the last.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const mtimeStamp = (fileSystem, files) =>
|
|
29
|
+
files.map((f) => `${f}:${fileSystem.mtimeMs(f) ?? -1}`).join('|');
|
|
30
|
+
|
|
31
|
+
export class ArtifactCache {
|
|
32
|
+
#fileSystem;
|
|
33
|
+
#entries = new Map(); // key -> { value, files, stamp }
|
|
34
|
+
#epoch = null;
|
|
35
|
+
|
|
36
|
+
constructor(fileSystem) {
|
|
37
|
+
this.#fileSystem = fileSystem;
|
|
38
|
+
this.hits = 0;
|
|
39
|
+
this.misses = 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Declare the build epoch. If it changed, the whole cache is invalid.
|
|
44
|
+
*/
|
|
45
|
+
epoch(stamp) {
|
|
46
|
+
if (stamp === this.#epoch) return;
|
|
47
|
+
this.#epoch = stamp;
|
|
48
|
+
this.#entries.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Serve the artifact from cache if no file of its graph changed; otherwise build and remember.
|
|
53
|
+
* `filesOf` — how to extract the graph from the result (default `watchFiles`).
|
|
54
|
+
*/
|
|
55
|
+
async memo(key, produce, filesOf = (v) => v.watchFiles ?? []) {
|
|
56
|
+
const hit = this.#entries.get(key);
|
|
57
|
+
if (hit && mtimeStamp(this.#fileSystem, hit.files) === hit.stamp) {
|
|
58
|
+
this.hits++;
|
|
59
|
+
return hit.value;
|
|
60
|
+
}
|
|
61
|
+
const value = await produce();
|
|
62
|
+
const files = [...new Set(filesOf(value))];
|
|
63
|
+
this.#entries.set(key, { value, files, stamp: mtimeStamp(this.#fileSystem, files) });
|
|
64
|
+
this.misses++;
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Counters for the LAST iteration — the dev server prints them to the log so that "only what
|
|
70
|
+
* changed got rebuilt" is visible, not assumed.
|
|
71
|
+
*/
|
|
72
|
+
take() {
|
|
73
|
+
const stats = { hits: this.hits, misses: this.misses, size: this.#entries.size };
|
|
74
|
+
this.hits = 0;
|
|
75
|
+
this.misses = 0;
|
|
76
|
+
return stats;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { path } from './nodeApi.mjs';
|
|
2
|
+
import { packageDirOf } from './packageMeta.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Legacy `browser` field in package.json.
|
|
6
|
+
*
|
|
7
|
+
* This is NOT an `exports` condition but an older, entirely different mechanism — a substitution
|
|
8
|
+
* map inside the package:
|
|
9
|
+
*
|
|
10
|
+
* "browser": { "./lib/node.js": "./lib/browser.js", "crypto": false }
|
|
11
|
+
*
|
|
12
|
+
* The value `false` means "this does not exist in the browser, substitute a stub". There is also
|
|
13
|
+
* a string form (`"browser": "./dist/browser.js"`) — an alternative package ENTRY; it applies
|
|
14
|
+
* only when there is no `exports` map, because `exports` is more modern and overrides the
|
|
15
|
+
* legacy fields.
|
|
16
|
+
*
|
|
17
|
+
* This used to be handled by `@rollup/plugin-node-resolve({ browser: true })`. After moving
|
|
18
|
+
* resolution onto the port the mechanism got lost: nothing in the current closure uses it
|
|
19
|
+
* (otherwise the artifact comparison would not have matched), but on a new dependency it would
|
|
20
|
+
* misfire as quietly as zod's conditional `exports` did.
|
|
21
|
+
*
|
|
22
|
+
* Lives OUTSIDE the port: it only needs `readJson` and the upward walk (`packageMeta`), so it
|
|
23
|
+
* works with any port implementation.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Stub specifier: `"…": false` in the map. The plugin serves an empty module for it. */
|
|
27
|
+
export const BROWSER_EMPTY = '\0mosetta-browser-empty';
|
|
28
|
+
|
|
29
|
+
const EXTENSIONS = ['', '.mjs', '.js', '.ts', '.tsx', '.json'];
|
|
30
|
+
|
|
31
|
+
function readPackage(fileSystem, dir) {
|
|
32
|
+
try {
|
|
33
|
+
return fileSystem.readJson(path.join(dir, 'package.json'));
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// map keys are written inconsistently: './lib/x.js', 'lib/x.js', './lib/x' — normalize both sides
|
|
40
|
+
function lookup(map, relative) {
|
|
41
|
+
const candidates = [relative, relative.replace(/^\.\//, '')];
|
|
42
|
+
for (const base of candidates) {
|
|
43
|
+
for (const ext of EXTENSIONS) {
|
|
44
|
+
const withExt = base.endsWith(ext) ? base : base + ext;
|
|
45
|
+
for (const key of [withExt, `./${withExt.replace(/^\.\//, '')}`]) {
|
|
46
|
+
if (Object.hasOwn(map, key)) return map[key];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// and the reverse case: the map has the path WITHOUT an extension, we have it with one
|
|
50
|
+
const stripped = base.replace(/\.(mjs|js|ts|tsx|json)$/, '');
|
|
51
|
+
for (const key of [stripped, `./${stripped.replace(/^\.\//, '')}`]) {
|
|
52
|
+
if (Object.hasOwn(map, key)) return map[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Apply the owning package's `browser` map to an already resolved ABSOLUTE path.
|
|
60
|
+
* Returns the path (possibly substituted), or `BROWSER_EMPTY` for `false`.
|
|
61
|
+
* Without the `browser` condition in the set — identity.
|
|
62
|
+
*/
|
|
63
|
+
export function applyBrowserField(fileSystem, file, conditions) {
|
|
64
|
+
if (!conditions?.includes('browser')) return file;
|
|
65
|
+
const dir = packageDirOf(fileSystem, file);
|
|
66
|
+
if (!dir) return file;
|
|
67
|
+
const pkg = readPackage(fileSystem, dir);
|
|
68
|
+
const map = pkg?.browser;
|
|
69
|
+
if (!map || typeof map !== 'object') return file;
|
|
70
|
+
const relative = `./${path.relative(dir, file).split(path.sep).join('/')}`;
|
|
71
|
+
const hit = lookup(map, relative);
|
|
72
|
+
if (hit === undefined) return file;
|
|
73
|
+
if (hit === false) return BROWSER_EMPTY;
|
|
74
|
+
return path.resolve(dir, hit);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The string form — an alternative package entry. Applies only to the ROOT specifier
|
|
79
|
+
* (`'lodash'` but not `'lodash/get'`) and only when there is no `exports` map.
|
|
80
|
+
*/
|
|
81
|
+
export function applyBrowserEntry(fileSystem, specifier, file, conditions) {
|
|
82
|
+
if (!conditions?.includes('browser')) return file;
|
|
83
|
+
if (specifier.startsWith('.') || path.isAbsolute(specifier)) return file;
|
|
84
|
+
const dir = packageDirOf(fileSystem, file);
|
|
85
|
+
if (!dir) return file;
|
|
86
|
+
const pkg = readPackage(fileSystem, dir);
|
|
87
|
+
if (!pkg || pkg.exports || typeof pkg.browser !== 'string') return file;
|
|
88
|
+
if (pkg.name && specifier !== pkg.name) return file; // subpath — the root entry does not apply
|
|
89
|
+
return path.resolve(dir, pkg.browser);
|
|
90
|
+
}
|