@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 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: what the next
2530
- // publish -- the Studio's included -- starts from.
2531
- projectSource: sources,
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: sources
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: git.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: what the next
2530
- // publish -- the Studio's included -- starts from.
2531
- projectSource: sources,
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: sources
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: git.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: what the next
2495
- // publish -- the Studio's included -- starts from.
2496
- projectSource: sources,
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: sources
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: git.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.0",
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.0",
29
+ "@valbuild/core": "0.138.2",
30
30
  "@valbuild/eslint-plugin": "0.125.0",
31
- "@valbuild/language-server": "0.138.0",
32
- "@valbuild/server": "0.138.0",
33
- "@valbuild/shared": "0.138.0"
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: "main",
156
+ branch,
144
157
  env: { VAL_PROJECT_TOKEN: TOKEN, VAL_CONTENT_URL: fake.url },
145
158
  sleep: () => Promise.resolve(),
146
- log: () => undefined,
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
- process.stdout.write(`${JSON.stringify({ fresh, held })}\n`);
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: what the next
299
- // publish -- the Studio's included -- starts from.
300
- projectSource: sources,
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: sources,
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: git.branch },
193
+ git: { commit: git.commit, branch: wiredBranch },
181
194
  env,
182
195
  log,
183
196
  });