@valbuild/cli 0.138.0 → 0.138.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/CHANGELOG.md +23 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +36 -5
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +36 -5
- package/cli/dist/valbuild-cli-cli.esm.js +36 -5
- package/package.json +5 -5
- package/src/publish/__fixtures__/buildPublishProbe.ts +51 -3
- package/src/publish/buildArtifacts.ts +29 -4
- package/src/publish/buildPublish.test.ts +25 -1
- package/src/publish/runPublish.ts +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @valbuild/cli
|
|
2
2
|
|
|
3
|
+
## 0.138.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#763](https://github.com/valbuild/val/pull/763) [`443cdcd`](https://github.com/valbuild/val/commit/443cdcdfa8948db22a54adef2ad5e18d9f9868e9) Thanks [@freekh](https://github.com/freekh)! - `val publish` now wires the site at the project's branch, which the content service names, instead of the branch the checkout is on. The site's Val saves its edits to the branch it is wired at, so publishing a managed project from a feature branch made every Studio edit land where no publish looks, and Publish answered "nothing to publish". When the two differ, the CLI says so. With a content service that does not name the branch, the checkout's is used, as before.
|
|
8
|
+
- Updated dependencies [[`2beb226`](https://github.com/valbuild/val/commit/2beb226446c76eb71026f43e04c13943da4a1e31), [`cf3bbc8`](https://github.com/valbuild/val/commit/cf3bbc8fc89ff349aac5ab101d0b4f28164583e6), [`443cdcd`](https://github.com/valbuild/val/commit/443cdcdfa8948db22a54adef2ad5e18d9f9868e9)]:
|
|
9
|
+
- @valbuild/shared@0.138.2
|
|
10
|
+
- @valbuild/core@0.138.2
|
|
11
|
+
- @valbuild/server@0.138.2
|
|
12
|
+
- @valbuild/language-server@0.138.2
|
|
13
|
+
|
|
14
|
+
## 0.138.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#758](https://github.com/valbuild/val/pull/758) [`af57406`](https://github.com/valbuild/val/commit/af57406fb679f5917559a8c97507b7c42f2b48fe) Thanks [@freekh](https://github.com/freekh)! - `val publish` no longer stores a project's stylesheets as its source. It still compiles them, so the site's CSS is unchanged, but the source a managed project's Studio builds from comes without them — as the platform's own publish has always stored it.
|
|
19
|
+
|
|
20
|
+
A stored stylesheet broke every Studio publish that followed: a browser build cannot run a Tailwind `@plugin` (the starter's `src/styles.css` has `@plugin "@tailwindcss/typography"`), so it refused the stylesheet and the publish failed with "build failed 3 times". Without one, the Studio ships the live site's CSS, which is right — a Studio edit never changes a stylesheet.
|
|
21
|
+
|
|
22
|
+
- Updated dependencies []:
|
|
23
|
+
- @valbuild/server@0.138.1
|
|
24
|
+
- @valbuild/language-server@0.138.1
|
|
25
|
+
|
|
3
26
|
## 0.138.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
|
@@ -2351,6 +2351,25 @@ const CODE_FILE = /\.(tsx?|jsx?|mjs|cjs|css)$/;
|
|
|
2351
2351
|
*/
|
|
2352
2352
|
const JSON_FILE = /\.json$/;
|
|
2353
2353
|
|
|
2354
|
+
/**
|
|
2355
|
+
* The project's source as it is STORED, which is what the Studio builds from:
|
|
2356
|
+
* every file this build read, less the stylesheets.
|
|
2357
|
+
*
|
|
2358
|
+
* This build compiles them, because it can run what a stylesheet asks for -- a
|
|
2359
|
+
* Tailwind `@plugin` is a module resolved and executed from `node_modules`. A
|
|
2360
|
+
* build in the browser cannot, and refuses the stylesheet rather than ship a
|
|
2361
|
+
* page that is quietly unstyled; the starter's `src/styles.css` has
|
|
2362
|
+
* `@plugin "@tailwindcss/typography"`. So a stored stylesheet made every
|
|
2363
|
+
* Studio publish after the first one fail. Without it, the Studio's build has
|
|
2364
|
+
* no CSS of its own and ships the live site's (`liveStylesheet` in the
|
|
2365
|
+
* Studio), which is right: a Studio save changes content and uploaded files,
|
|
2366
|
+
* never a stylesheet. It is also what the platform's own publish has always
|
|
2367
|
+
* stored.
|
|
2368
|
+
*/
|
|
2369
|
+
function withoutStylesheets(sources) {
|
|
2370
|
+
return Object.fromEntries(Object.entries(sources).filter(([key]) => !key.endsWith(".css")));
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2354
2373
|
/**
|
|
2355
2374
|
* The project's own files: its source as text, and the binaries it imports
|
|
2356
2375
|
* (an image, a font) base64, as `BuildInput.assets` takes them. `public/` is
|
|
@@ -2487,6 +2506,7 @@ async function buildArtifacts(options) {
|
|
|
2487
2506
|
// Less the one about the layer, which is the thing this goes on to build.
|
|
2488
2507
|
warnings.push(...((wiring === null || wiring === void 0 ? void 0 : wiring.warnings) ?? []).filter(warning => !warning.includes("dependency layer built for it")));
|
|
2489
2508
|
const sources = (wiring === null || wiring === void 0 ? void 0 : wiring.files) ?? onDisk;
|
|
2509
|
+
const projectSource = withoutStylesheets(sources);
|
|
2490
2510
|
|
|
2491
2511
|
// The dependency layer: from what the project imports, of what it declares.
|
|
2492
2512
|
log("Building the dependency layer");
|
|
@@ -2526,9 +2546,10 @@ async function buildArtifacts(options) {
|
|
|
2526
2546
|
env: publicEnv(options.env ?? process.env),
|
|
2527
2547
|
publicFiles: readPublicFiles(root),
|
|
2528
2548
|
assets,
|
|
2529
|
-
// The project's own files, without the generated tree
|
|
2530
|
-
// publish -- the Studio's included -- starts
|
|
2531
|
-
|
|
2549
|
+
// The project's own files, without the generated tree or the
|
|
2550
|
+
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
2551
|
+
// from. See `withoutStylesheets`.
|
|
2552
|
+
projectSource,
|
|
2532
2553
|
loadCssModule: node.cssModuleLoader(root),
|
|
2533
2554
|
...(splitter ? {
|
|
2534
2555
|
routeSplitter: splitter
|
|
@@ -2536,7 +2557,7 @@ async function buildArtifacts(options) {
|
|
|
2536
2557
|
rsc: target.project.rsc
|
|
2537
2558
|
});
|
|
2538
2559
|
const artifacts = await builder.publishArtifacts(built, {
|
|
2539
|
-
projectSource
|
|
2560
|
+
projectSource
|
|
2540
2561
|
});
|
|
2541
2562
|
const dir = options.outDir ?? fs__default$1["default"].mkdtempSync(path__default["default"].join(os__default["default"].tmpdir(), "val-publish-"));
|
|
2542
2563
|
for (const artifact of artifacts) {
|
|
@@ -3121,6 +3142,16 @@ async function runPublish(options) {
|
|
|
3121
3142
|
if (collected === null) {
|
|
3122
3143
|
log("Asking content what to build against");
|
|
3123
3144
|
const target = await client.buildTarget();
|
|
3145
|
+
/*
|
|
3146
|
+
* Wired at the PROJECT's branch, which content names, not the
|
|
3147
|
+
* checkout's: the build's Val saves its edits to the branch it is wired
|
|
3148
|
+
* at, and a publish only ever commits the project's. Wired at a feature
|
|
3149
|
+
* branch, every Studio edit was saved where no publish looks.
|
|
3150
|
+
*/
|
|
3151
|
+
const wiredBranch = target.branch ?? git.branch;
|
|
3152
|
+
if (wiredBranch !== git.branch) {
|
|
3153
|
+
log(`warn: this checkout is on ${git.branch}; the project publishes ${wiredBranch}, ` + `so the site saves its edits to ${wiredBranch}.`);
|
|
3154
|
+
}
|
|
3124
3155
|
const build = await buildArtifacts({
|
|
3125
3156
|
root,
|
|
3126
3157
|
target,
|
|
@@ -3128,7 +3159,7 @@ async function runPublish(options) {
|
|
|
3128
3159
|
contentHost,
|
|
3129
3160
|
git: {
|
|
3130
3161
|
commit: git.commit,
|
|
3131
|
-
branch:
|
|
3162
|
+
branch: wiredBranch
|
|
3132
3163
|
},
|
|
3133
3164
|
env,
|
|
3134
3165
|
log
|
|
@@ -2351,6 +2351,25 @@ const CODE_FILE = /\.(tsx?|jsx?|mjs|cjs|css)$/;
|
|
|
2351
2351
|
*/
|
|
2352
2352
|
const JSON_FILE = /\.json$/;
|
|
2353
2353
|
|
|
2354
|
+
/**
|
|
2355
|
+
* The project's source as it is STORED, which is what the Studio builds from:
|
|
2356
|
+
* every file this build read, less the stylesheets.
|
|
2357
|
+
*
|
|
2358
|
+
* This build compiles them, because it can run what a stylesheet asks for -- a
|
|
2359
|
+
* Tailwind `@plugin` is a module resolved and executed from `node_modules`. A
|
|
2360
|
+
* build in the browser cannot, and refuses the stylesheet rather than ship a
|
|
2361
|
+
* page that is quietly unstyled; the starter's `src/styles.css` has
|
|
2362
|
+
* `@plugin "@tailwindcss/typography"`. So a stored stylesheet made every
|
|
2363
|
+
* Studio publish after the first one fail. Without it, the Studio's build has
|
|
2364
|
+
* no CSS of its own and ships the live site's (`liveStylesheet` in the
|
|
2365
|
+
* Studio), which is right: a Studio save changes content and uploaded files,
|
|
2366
|
+
* never a stylesheet. It is also what the platform's own publish has always
|
|
2367
|
+
* stored.
|
|
2368
|
+
*/
|
|
2369
|
+
function withoutStylesheets(sources) {
|
|
2370
|
+
return Object.fromEntries(Object.entries(sources).filter(([key]) => !key.endsWith(".css")));
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2354
2373
|
/**
|
|
2355
2374
|
* The project's own files: its source as text, and the binaries it imports
|
|
2356
2375
|
* (an image, a font) base64, as `BuildInput.assets` takes them. `public/` is
|
|
@@ -2487,6 +2506,7 @@ async function buildArtifacts(options) {
|
|
|
2487
2506
|
// Less the one about the layer, which is the thing this goes on to build.
|
|
2488
2507
|
warnings.push(...((wiring === null || wiring === void 0 ? void 0 : wiring.warnings) ?? []).filter(warning => !warning.includes("dependency layer built for it")));
|
|
2489
2508
|
const sources = (wiring === null || wiring === void 0 ? void 0 : wiring.files) ?? onDisk;
|
|
2509
|
+
const projectSource = withoutStylesheets(sources);
|
|
2490
2510
|
|
|
2491
2511
|
// The dependency layer: from what the project imports, of what it declares.
|
|
2492
2512
|
log("Building the dependency layer");
|
|
@@ -2526,9 +2546,10 @@ async function buildArtifacts(options) {
|
|
|
2526
2546
|
env: publicEnv(options.env ?? process.env),
|
|
2527
2547
|
publicFiles: readPublicFiles(root),
|
|
2528
2548
|
assets,
|
|
2529
|
-
// The project's own files, without the generated tree
|
|
2530
|
-
// publish -- the Studio's included -- starts
|
|
2531
|
-
|
|
2549
|
+
// The project's own files, without the generated tree or the
|
|
2550
|
+
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
2551
|
+
// from. See `withoutStylesheets`.
|
|
2552
|
+
projectSource,
|
|
2532
2553
|
loadCssModule: node.cssModuleLoader(root),
|
|
2533
2554
|
...(splitter ? {
|
|
2534
2555
|
routeSplitter: splitter
|
|
@@ -2536,7 +2557,7 @@ async function buildArtifacts(options) {
|
|
|
2536
2557
|
rsc: target.project.rsc
|
|
2537
2558
|
});
|
|
2538
2559
|
const artifacts = await builder.publishArtifacts(built, {
|
|
2539
|
-
projectSource
|
|
2560
|
+
projectSource
|
|
2540
2561
|
});
|
|
2541
2562
|
const dir = options.outDir ?? fs__default$1["default"].mkdtempSync(path__default["default"].join(os__default["default"].tmpdir(), "val-publish-"));
|
|
2542
2563
|
for (const artifact of artifacts) {
|
|
@@ -3121,6 +3142,16 @@ async function runPublish(options) {
|
|
|
3121
3142
|
if (collected === null) {
|
|
3122
3143
|
log("Asking content what to build against");
|
|
3123
3144
|
const target = await client.buildTarget();
|
|
3145
|
+
/*
|
|
3146
|
+
* Wired at the PROJECT's branch, which content names, not the
|
|
3147
|
+
* checkout's: the build's Val saves its edits to the branch it is wired
|
|
3148
|
+
* at, and a publish only ever commits the project's. Wired at a feature
|
|
3149
|
+
* branch, every Studio edit was saved where no publish looks.
|
|
3150
|
+
*/
|
|
3151
|
+
const wiredBranch = target.branch ?? git.branch;
|
|
3152
|
+
if (wiredBranch !== git.branch) {
|
|
3153
|
+
log(`warn: this checkout is on ${git.branch}; the project publishes ${wiredBranch}, ` + `so the site saves its edits to ${wiredBranch}.`);
|
|
3154
|
+
}
|
|
3124
3155
|
const build = await buildArtifacts({
|
|
3125
3156
|
root,
|
|
3126
3157
|
target,
|
|
@@ -3128,7 +3159,7 @@ async function runPublish(options) {
|
|
|
3128
3159
|
contentHost,
|
|
3129
3160
|
git: {
|
|
3130
3161
|
commit: git.commit,
|
|
3131
|
-
branch:
|
|
3162
|
+
branch: wiredBranch
|
|
3132
3163
|
},
|
|
3133
3164
|
env,
|
|
3134
3165
|
log
|
|
@@ -2316,6 +2316,25 @@ const CODE_FILE = /\.(tsx?|jsx?|mjs|cjs|css)$/;
|
|
|
2316
2316
|
*/
|
|
2317
2317
|
const JSON_FILE = /\.json$/;
|
|
2318
2318
|
|
|
2319
|
+
/**
|
|
2320
|
+
* The project's source as it is STORED, which is what the Studio builds from:
|
|
2321
|
+
* every file this build read, less the stylesheets.
|
|
2322
|
+
*
|
|
2323
|
+
* This build compiles them, because it can run what a stylesheet asks for -- a
|
|
2324
|
+
* Tailwind `@plugin` is a module resolved and executed from `node_modules`. A
|
|
2325
|
+
* build in the browser cannot, and refuses the stylesheet rather than ship a
|
|
2326
|
+
* page that is quietly unstyled; the starter's `src/styles.css` has
|
|
2327
|
+
* `@plugin "@tailwindcss/typography"`. So a stored stylesheet made every
|
|
2328
|
+
* Studio publish after the first one fail. Without it, the Studio's build has
|
|
2329
|
+
* no CSS of its own and ships the live site's (`liveStylesheet` in the
|
|
2330
|
+
* Studio), which is right: a Studio save changes content and uploaded files,
|
|
2331
|
+
* never a stylesheet. It is also what the platform's own publish has always
|
|
2332
|
+
* stored.
|
|
2333
|
+
*/
|
|
2334
|
+
function withoutStylesheets(sources) {
|
|
2335
|
+
return Object.fromEntries(Object.entries(sources).filter(([key]) => !key.endsWith(".css")));
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2319
2338
|
/**
|
|
2320
2339
|
* The project's own files: its source as text, and the binaries it imports
|
|
2321
2340
|
* (an image, a font) base64, as `BuildInput.assets` takes them. `public/` is
|
|
@@ -2452,6 +2471,7 @@ async function buildArtifacts(options) {
|
|
|
2452
2471
|
// Less the one about the layer, which is the thing this goes on to build.
|
|
2453
2472
|
warnings.push(...((wiring === null || wiring === void 0 ? void 0 : wiring.warnings) ?? []).filter(warning => !warning.includes("dependency layer built for it")));
|
|
2454
2473
|
const sources = (wiring === null || wiring === void 0 ? void 0 : wiring.files) ?? onDisk;
|
|
2474
|
+
const projectSource = withoutStylesheets(sources);
|
|
2455
2475
|
|
|
2456
2476
|
// The dependency layer: from what the project imports, of what it declares.
|
|
2457
2477
|
log("Building the dependency layer");
|
|
@@ -2491,9 +2511,10 @@ async function buildArtifacts(options) {
|
|
|
2491
2511
|
env: publicEnv(options.env ?? process.env),
|
|
2492
2512
|
publicFiles: readPublicFiles(root),
|
|
2493
2513
|
assets,
|
|
2494
|
-
// The project's own files, without the generated tree
|
|
2495
|
-
// publish -- the Studio's included -- starts
|
|
2496
|
-
|
|
2514
|
+
// The project's own files, without the generated tree or the
|
|
2515
|
+
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
2516
|
+
// from. See `withoutStylesheets`.
|
|
2517
|
+
projectSource,
|
|
2497
2518
|
loadCssModule: node.cssModuleLoader(root),
|
|
2498
2519
|
...(splitter ? {
|
|
2499
2520
|
routeSplitter: splitter
|
|
@@ -2501,7 +2522,7 @@ async function buildArtifacts(options) {
|
|
|
2501
2522
|
rsc: target.project.rsc
|
|
2502
2523
|
});
|
|
2503
2524
|
const artifacts = await builder.publishArtifacts(built, {
|
|
2504
|
-
projectSource
|
|
2525
|
+
projectSource
|
|
2505
2526
|
});
|
|
2506
2527
|
const dir = options.outDir ?? fs$1.mkdtempSync(path.join(os.tmpdir(), "val-publish-"));
|
|
2507
2528
|
for (const artifact of artifacts) {
|
|
@@ -3086,6 +3107,16 @@ async function runPublish(options) {
|
|
|
3086
3107
|
if (collected === null) {
|
|
3087
3108
|
log("Asking content what to build against");
|
|
3088
3109
|
const target = await client.buildTarget();
|
|
3110
|
+
/*
|
|
3111
|
+
* Wired at the PROJECT's branch, which content names, not the
|
|
3112
|
+
* checkout's: the build's Val saves its edits to the branch it is wired
|
|
3113
|
+
* at, and a publish only ever commits the project's. Wired at a feature
|
|
3114
|
+
* branch, every Studio edit was saved where no publish looks.
|
|
3115
|
+
*/
|
|
3116
|
+
const wiredBranch = target.branch ?? git.branch;
|
|
3117
|
+
if (wiredBranch !== git.branch) {
|
|
3118
|
+
log(`warn: this checkout is on ${git.branch}; the project publishes ${wiredBranch}, ` + `so the site saves its edits to ${wiredBranch}.`);
|
|
3119
|
+
}
|
|
3089
3120
|
const build = await buildArtifacts({
|
|
3090
3121
|
root,
|
|
3091
3122
|
target,
|
|
@@ -3093,7 +3124,7 @@ async function runPublish(options) {
|
|
|
3093
3124
|
contentHost,
|
|
3094
3125
|
git: {
|
|
3095
3126
|
commit: git.commit,
|
|
3096
|
-
branch:
|
|
3127
|
+
branch: wiredBranch
|
|
3097
3128
|
},
|
|
3098
3129
|
env,
|
|
3099
3130
|
log
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.138.
|
|
4
|
+
"version": "0.138.2",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
28
|
"zod-validation-error": "^5.0.0",
|
|
29
|
-
"@valbuild/core": "0.138.
|
|
29
|
+
"@valbuild/core": "0.138.2",
|
|
30
30
|
"@valbuild/eslint-plugin": "0.125.0",
|
|
31
|
-
"@valbuild/language-server": "0.138.
|
|
32
|
-
"@valbuild/server": "0.138.
|
|
33
|
-
"@valbuild/shared": "0.138.
|
|
31
|
+
"@valbuild/language-server": "0.138.2",
|
|
32
|
+
"@valbuild/server": "0.138.2",
|
|
33
|
+
"@valbuild/shared": "0.138.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"prettier": "*",
|
|
@@ -68,6 +68,9 @@ export const Route = createFileRoute("/build/")({
|
|
|
68
68
|
});
|
|
69
69
|
`,
|
|
70
70
|
"src/styles.css": `h1 { color: rebeccapurple; }\n`,
|
|
71
|
+
// A Val project, so the publish wires it -- and wires it at a branch.
|
|
72
|
+
"val.config.ts": `export const config = { project: "acme/site" };\n`,
|
|
73
|
+
"val.modules.ts": `export default { modules: [] };\n`,
|
|
71
74
|
"public/robots.txt": "User-agent: *\n",
|
|
72
75
|
};
|
|
73
76
|
|
|
@@ -86,6 +89,10 @@ type Report = {
|
|
|
86
89
|
uploadedLayerRev: string | null;
|
|
87
90
|
/** Whether the source artifact holds the project's own route file. */
|
|
88
91
|
sourceHasRoute: boolean;
|
|
92
|
+
/** Whether the source artifact holds a stylesheet -- it must not. */
|
|
93
|
+
sourceHasStylesheet: boolean;
|
|
94
|
+
/** Whether the stylesheet was compiled into the site's CSS all the same. */
|
|
95
|
+
cssHasRule: boolean;
|
|
89
96
|
/** Whether the client build carries the page's text. */
|
|
90
97
|
clientHasPage: boolean;
|
|
91
98
|
/** Whether the imported image reached the build, inlined. */
|
|
@@ -94,6 +101,10 @@ type Report = {
|
|
|
94
101
|
buildRouteBuilt: boolean;
|
|
95
102
|
/** Whether the checkout was left exactly as it was found. */
|
|
96
103
|
checkoutUntouched: boolean;
|
|
104
|
+
/** The branch the stored `val.server.ts` is wired at. */
|
|
105
|
+
wiredBranch: string | null;
|
|
106
|
+
/** Whether the run warned that the checkout's branch is not the project's. */
|
|
107
|
+
warnedAboutBranch: boolean;
|
|
97
108
|
};
|
|
98
109
|
|
|
99
110
|
/** A 1x1 PNG: small enough to inline, so the page carries it as a data URI. */
|
|
@@ -135,15 +146,19 @@ function listed(root: string): string[] {
|
|
|
135
146
|
async function publishOnce(
|
|
136
147
|
root: string,
|
|
137
148
|
fake: FakeContentService,
|
|
149
|
+
branch = "main",
|
|
138
150
|
): Promise<Report> {
|
|
139
151
|
const before = listed(root);
|
|
152
|
+
const logged: string[] = [];
|
|
140
153
|
const result = await runPublish({
|
|
141
154
|
root,
|
|
142
155
|
commit: COMMIT,
|
|
143
|
-
branch
|
|
156
|
+
branch,
|
|
144
157
|
env: { VAL_PROJECT_TOKEN: TOKEN, VAL_CONTENT_URL: fake.url },
|
|
145
158
|
sleep: () => Promise.resolve(),
|
|
146
|
-
log: () =>
|
|
159
|
+
log: (line: string) => {
|
|
160
|
+
logged.push(line);
|
|
161
|
+
},
|
|
147
162
|
});
|
|
148
163
|
const declared = [...fake.declarations.values()].map((body) => {
|
|
149
164
|
const d = body as {
|
|
@@ -183,6 +198,14 @@ async function publishOnce(
|
|
|
183
198
|
? "src/routes/index.tsx" in
|
|
184
199
|
(JSON.parse(source.toString("utf8")) as Record<string, string>)
|
|
185
200
|
: false,
|
|
201
|
+
sourceHasStylesheet: source
|
|
202
|
+
? Object.keys(
|
|
203
|
+
JSON.parse(source.toString("utf8")) as Record<string, string>,
|
|
204
|
+
).some((key) => key.endsWith(".css"))
|
|
205
|
+
: false,
|
|
206
|
+
cssHasRule: (fake.stored.get("css")?.toString("utf8") ?? "").includes(
|
|
207
|
+
"rebeccapurple",
|
|
208
|
+
),
|
|
186
209
|
clientHasPage: clientText.includes("hello from the checkout"),
|
|
187
210
|
clientHasLogo: clientText.includes(
|
|
188
211
|
`data:image/png;base64,${LOGO.toString("base64")}`,
|
|
@@ -194,9 +217,25 @@ async function publishOnce(
|
|
|
194
217
|
(JSON.parse(source.toString("utf8")) as Record<string, string>)
|
|
195
218
|
: false),
|
|
196
219
|
checkoutUntouched: JSON.stringify(listed(root)) === JSON.stringify(before),
|
|
220
|
+
wiredBranch: source ? wiredBranchOf(source) : null,
|
|
221
|
+
warnedAboutBranch: logged.some(
|
|
222
|
+
(line) => line.startsWith("warn:") && line.includes("publishes"),
|
|
223
|
+
),
|
|
197
224
|
};
|
|
198
225
|
}
|
|
199
226
|
|
|
227
|
+
/** The branch the stored `val.server.ts` was wired at: its `BUILT_FROM`. */
|
|
228
|
+
function wiredBranchOf(source: Buffer): string | null {
|
|
229
|
+
const files = JSON.parse(source.toString("utf8")) as Record<string, string>;
|
|
230
|
+
const server = Object.entries(files).find(([key]) =>
|
|
231
|
+
key.endsWith("val.server.ts"),
|
|
232
|
+
)?.[1];
|
|
233
|
+
const match = server ? /^const BUILT_FROM = (.+);$/m.exec(server) : null;
|
|
234
|
+
if (!match?.[1] || match[1] === "null") return null;
|
|
235
|
+
const parsed = JSON.parse(match[1]) as { branch?: unknown };
|
|
236
|
+
return typeof parsed.branch === "string" ? parsed.branch : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
200
239
|
(async () => {
|
|
201
240
|
const root = writeProject();
|
|
202
241
|
try {
|
|
@@ -216,7 +255,16 @@ async function publishOnce(
|
|
|
216
255
|
});
|
|
217
256
|
const held = await publishOnce(root, second).finally(() => second.close());
|
|
218
257
|
|
|
219
|
-
|
|
258
|
+
// From a checkout on a feature branch, of a project content says is on main.
|
|
259
|
+
const third = await startFakeContentService({
|
|
260
|
+
token: TOKEN,
|
|
261
|
+
buildTarget: { ...FAKE_BUILD_TARGET, branch: "main" },
|
|
262
|
+
});
|
|
263
|
+
const elsewhere = await publishOnce(root, third, "feature/x").finally(() =>
|
|
264
|
+
third.close(),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
process.stdout.write(`${JSON.stringify({ fresh, held, elsewhere })}\n`);
|
|
220
268
|
} finally {
|
|
221
269
|
fs.rmSync(root, { recursive: true, force: true });
|
|
222
270
|
}
|
|
@@ -97,6 +97,29 @@ const CODE_FILE = /\.(tsx?|jsx?|mjs|cjs|css)$/;
|
|
|
97
97
|
*/
|
|
98
98
|
const JSON_FILE = /\.json$/;
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The project's source as it is STORED, which is what the Studio builds from:
|
|
102
|
+
* every file this build read, less the stylesheets.
|
|
103
|
+
*
|
|
104
|
+
* This build compiles them, because it can run what a stylesheet asks for -- a
|
|
105
|
+
* Tailwind `@plugin` is a module resolved and executed from `node_modules`. A
|
|
106
|
+
* build in the browser cannot, and refuses the stylesheet rather than ship a
|
|
107
|
+
* page that is quietly unstyled; the starter's `src/styles.css` has
|
|
108
|
+
* `@plugin "@tailwindcss/typography"`. So a stored stylesheet made every
|
|
109
|
+
* Studio publish after the first one fail. Without it, the Studio's build has
|
|
110
|
+
* no CSS of its own and ships the live site's (`liveStylesheet` in the
|
|
111
|
+
* Studio), which is right: a Studio save changes content and uploaded files,
|
|
112
|
+
* never a stylesheet. It is also what the platform's own publish has always
|
|
113
|
+
* stored.
|
|
114
|
+
*/
|
|
115
|
+
export function withoutStylesheets(
|
|
116
|
+
sources: Record<string, string>,
|
|
117
|
+
): Record<string, string> {
|
|
118
|
+
return Object.fromEntries(
|
|
119
|
+
Object.entries(sources).filter(([key]) => !key.endsWith(".css")),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
100
123
|
/**
|
|
101
124
|
* The project's own files: its source as text, and the binaries it imports
|
|
102
125
|
* (an image, a font) base64, as `BuildInput.assets` takes them. `public/` is
|
|
@@ -244,6 +267,7 @@ export async function buildArtifacts(options: {
|
|
|
244
267
|
),
|
|
245
268
|
);
|
|
246
269
|
const sources = wiring?.files ?? onDisk;
|
|
270
|
+
const projectSource = withoutStylesheets(sources);
|
|
247
271
|
|
|
248
272
|
// The dependency layer: from what the project imports, of what it declares.
|
|
249
273
|
log("Building the dependency layer");
|
|
@@ -295,15 +319,16 @@ export async function buildArtifacts(options: {
|
|
|
295
319
|
env: publicEnv(options.env ?? process.env),
|
|
296
320
|
publicFiles: readPublicFiles(root),
|
|
297
321
|
assets,
|
|
298
|
-
// The project's own files, without the generated tree
|
|
299
|
-
// publish -- the Studio's included -- starts
|
|
300
|
-
|
|
322
|
+
// The project's own files, without the generated tree or the
|
|
323
|
+
// stylesheets: what the next publish -- the Studio's included -- starts
|
|
324
|
+
// from. See `withoutStylesheets`.
|
|
325
|
+
projectSource,
|
|
301
326
|
loadCssModule: node.cssModuleLoader(root),
|
|
302
327
|
...(splitter ? { routeSplitter: splitter } : {}),
|
|
303
328
|
rsc: target.project.rsc,
|
|
304
329
|
});
|
|
305
330
|
const artifacts: PublishArtifact[] = await builder.publishArtifacts(built, {
|
|
306
|
-
projectSource
|
|
331
|
+
projectSource,
|
|
307
332
|
});
|
|
308
333
|
|
|
309
334
|
const dir =
|
|
@@ -29,13 +29,17 @@ type Run = {
|
|
|
29
29
|
buildTargetCalls: number;
|
|
30
30
|
uploadedLayerRev: string | null;
|
|
31
31
|
sourceHasRoute: boolean;
|
|
32
|
+
sourceHasStylesheet: boolean;
|
|
33
|
+
cssHasRule: boolean;
|
|
32
34
|
clientHasPage: boolean;
|
|
33
35
|
clientHasLogo: boolean;
|
|
34
36
|
buildRouteBuilt: boolean;
|
|
35
37
|
checkoutUntouched: boolean;
|
|
38
|
+
wiredBranch: string | null;
|
|
39
|
+
warnedAboutBranch: boolean;
|
|
36
40
|
};
|
|
37
41
|
|
|
38
|
-
let probe: { fresh: Run; held: Run };
|
|
42
|
+
let probe: { fresh: Run; held: Run; elsewhere: Run };
|
|
39
43
|
|
|
40
44
|
beforeAll(() => {
|
|
41
45
|
const out = execFileSync(
|
|
@@ -76,6 +80,14 @@ test("the stored source is the project's own files", () => {
|
|
|
76
80
|
expect(probe.fresh.sourceHasRoute).toBe(true);
|
|
77
81
|
});
|
|
78
82
|
|
|
83
|
+
test("a stylesheet is built, and not stored as source", () => {
|
|
84
|
+
// The Studio builds from the stored source, in a browser that cannot run a
|
|
85
|
+
// Tailwind `@plugin`; a stored stylesheet failed every Studio publish after
|
|
86
|
+
// the first. Without one it ships the live site's CSS, compiled here.
|
|
87
|
+
expect(probe.fresh.cssHasRule).toBe(true);
|
|
88
|
+
expect(probe.fresh.sourceHasStylesheet).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
79
91
|
test("an image the project imports reaches the build", () => {
|
|
80
92
|
expect(probe.fresh.clientHasLogo).toBe(true);
|
|
81
93
|
});
|
|
@@ -100,6 +112,18 @@ test("a layer content already holds is named, not sent again", () => {
|
|
|
100
112
|
expect(held.declared[0]!.buildHash).toBe(fresh.declared[0]!.buildHash);
|
|
101
113
|
});
|
|
102
114
|
|
|
115
|
+
test("a checkout on another branch is wired at the project's, and says so", () => {
|
|
116
|
+
// Content names the project's branch; the site's Val saves its edits to the
|
|
117
|
+
// branch it is wired at, so a feature branch here was a site whose every
|
|
118
|
+
// Studio edit went where no publish looks.
|
|
119
|
+
expect(probe.elsewhere.result.status).toBe("live");
|
|
120
|
+
expect(probe.elsewhere.wiredBranch).toBe("main");
|
|
121
|
+
expect(probe.elsewhere.warnedAboutBranch).toBe(true);
|
|
122
|
+
// Where they agree there is nothing to say.
|
|
123
|
+
expect(probe.fresh.wiredBranch).toBe("main");
|
|
124
|
+
expect(probe.fresh.warnedAboutBranch).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
103
127
|
test("the checkout is left as it was found", () => {
|
|
104
128
|
// The route tree is generated on disk and put back; the build is written
|
|
105
129
|
// to a temporary directory, not into `.val/publish`.
|
|
@@ -172,12 +172,25 @@ export async function runPublish(
|
|
|
172
172
|
if (collected === null) {
|
|
173
173
|
log("Asking content what to build against");
|
|
174
174
|
const target = await client.buildTarget();
|
|
175
|
+
/*
|
|
176
|
+
* Wired at the PROJECT's branch, which content names, not the
|
|
177
|
+
* checkout's: the build's Val saves its edits to the branch it is wired
|
|
178
|
+
* at, and a publish only ever commits the project's. Wired at a feature
|
|
179
|
+
* branch, every Studio edit was saved where no publish looks.
|
|
180
|
+
*/
|
|
181
|
+
const wiredBranch = target.branch ?? git.branch;
|
|
182
|
+
if (wiredBranch !== git.branch) {
|
|
183
|
+
log(
|
|
184
|
+
`warn: this checkout is on ${git.branch}; the project publishes ${wiredBranch}, ` +
|
|
185
|
+
`so the site saves its edits to ${wiredBranch}.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
175
188
|
const build = await buildArtifacts({
|
|
176
189
|
root,
|
|
177
190
|
target,
|
|
178
191
|
project: project ?? "",
|
|
179
192
|
contentHost,
|
|
180
|
-
git: { commit: git.commit, branch:
|
|
193
|
+
git: { commit: git.commit, branch: wiredBranch },
|
|
181
194
|
env,
|
|
182
195
|
log,
|
|
183
196
|
});
|