Files
airship/apps/cli/scripts/vendor-assets.mjs
T
Nayan 83b1ab0520 fix(build): make a fresh clone build on Windows
A fresh clone could not build on Windows at all. Three independent causes,
reported by @kevin101681 in #11:

- check-css.mjs resolved the styles directory with `new URL(...).pathname`,
  which yields `/C:/Users/...`; readdirSync resolves that against the current
  drive and looks for `C:\C:\Users\...`. fileURLToPath is also the fix for a
  latent bug everywhere else: `.pathname` stays percent-encoded, so a checkout
  under a directory with a space in it fails on macOS and Linux too.
- vendor-assets.mjs derived asset names with `from.split("/").pop()`, a no-op
  on the backslash paths require.resolve returns, so the whole absolute source
  path was appended to the destination. path.basename handles both separators.
- The front-matter regexes in the three gen.mjs scripts were anchored to
  `^---\n`, and with core.autocrlf=true — the Git-for-Windows default — the
  markdown specs check out as CRLF, so they reported a missing front-matter
  block rather than a wrong one.

.gitattributes pins every checkout to LF, which prevents the third from
recurring. The regexes take `\r?\n` anyway: .gitattributes only applies on
checkout, so it does nothing for a clone made before it existed, a zip
download, a patch, or an editor configured to write CRLF.

sync-readme.mjs had the same defect one step further on, and it was the worse
one — it splits on "\n" but rejoins the banner with LF, so on a CRLF tree the
--check comparison could never match the file on disk and a Windows
contributor had no way to make that CI gate pass.
2026-08-11 23:10:43 +05:30

95 lines
3.3 KiB
JavaScript

// Copies the runtime assets the server serves but cannot import into the CLI's
// own dist/, so the published tarball is self-contained.
//
// Why this exists: packages/server/src/proxy.ts serves three things it resolves
// at runtime rather than importing — the overlay IIFE, the bippy hook IIFE, and
// the editor's woff2 fonts. They are assets, so no bundler can inline them.
//
// In this monorepo they resolve fine through node_modules. In a published
// install they do not exist at all: tsup inlines @airship/server into the CLI
// bundle, so proxy.ts's import.meta.url ends up pointing at the installed CLI's
// dist/, where no @airship/* package is on disk. dist/vendor/ is the copy that
// makes that case work. See the fallback in proxy.ts.
//
// Sources are resolved through the packages' own export maps rather than
// hardcoded ../../packages/… paths, so a layout change there fails loudly here
// instead of silently shipping a stale asset.
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
import { createRequire } from "node:module";
import { basename, join } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const distDir = fileURLToPath(new URL("../dist", import.meta.url));
const vendorDir = join(distDir, "vendor");
const fontsDir = join(vendorDir, "fonts");
const FONTS = [
"inter-variable.woff2",
"jetbrains-mono-400.woff2",
"jetbrains-mono-700.woff2",
];
function fail(what, cause) {
console.error(`vendor-assets: could not resolve ${what}`);
console.error(` ${cause}`);
console.error(" Build the workspace first: make build");
process.exit(1);
}
function resolve(specifier, what) {
try {
return require.resolve(specifier);
} catch (error) {
fail(what, error.message);
}
}
// Copies a file and, when present, the sourcemap sitting next to it. proxy.ts
// serves `${bundle}.map` derived from whatever path it resolved, so the map has
// to land beside its bundle here too.
function copyWithMap(from, toDir) {
// basename, not split("/"): require.resolve returns a native path, so on
// Windows there is no "/" to split on and `name` would come back as the whole
// absolute path — which join() then appends to the destination wholesale.
const name = basename(from);
copyFileSync(from, join(toDir, name));
if (existsSync(`${from}.map`)) {
copyFileSync(`${from}.map`, join(toDir, `${name}.map`));
}
}
if (!existsSync(distDir)) {
fail(
"dist/",
"the CLI has not been built yet — tsup runs before this script"
);
}
mkdirSync(fontsDir, { recursive: true });
copyWithMap(
resolve("@airship/overlay/bundle", "the overlay bundle"),
vendorDir
);
copyWithMap(resolve("@airship/overlay/hook", "the bippy hook"), vendorDir);
for (const font of FONTS) {
const from = resolve(`@airship/editor-tokens/fonts/${font}`, `font ${font}`);
copyFileSync(from, join(fontsDir, font));
}
// Belt and braces: a silently empty vendor/ is the exact failure this script
// exists to prevent, and it would only surface as a 404 in a published install.
const expected = ["overlay.global.js", "hook.global.js"];
for (const name of expected) {
if (!existsSync(join(vendorDir, name))) {
fail(name, `it was not written to ${vendorDir}`);
}
}
console.log(
`vendor-assets: ${expected.length} bundles + ${FONTS.length} fonts -> ${vendorDir}`
);