@biffo/cli 0.303.4 → 0.304.1

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/dist/index.js CHANGED
@@ -4993,6 +4993,15 @@ var DnsSchema = z5.object({
4993
4993
  mode: z5.enum(["managed-route53", "external", "none"]).default("managed-route53"),
4994
4994
  domain: z5.string().min(1).optional()
4995
4995
  });
4996
+ var DesignTokensSchema = z5.object({
4997
+ package: z5.string().min(1).describe(
4998
+ `npm package providing this instance's real type scale, e.g. "@tabsii-com/ui". Added as an apps/frontend dependency of every new sibling, alongside (not instead of) @biffo/design-tokens -- biffo-scale-guard's own bin still ships from there.`
4999
+ ),
5000
+ version: z5.string().min(1).default("*").describe("Version range to pin `package` at in a new sibling's apps/frontend/package.json"),
5001
+ path: z5.string().min(1).describe(
5002
+ `Path to the CSS file declaring the --text-*/--space-* scale, relative to the package root (e.g. "dist/tokens.css"). Used for both the new sibling's globals.css @import and biffo-scale-guard's --tokens argument.`
5003
+ )
5004
+ });
4996
5005
  var BiffoConfigSchema = z5.object({
4997
5006
  $schema: z5.string().optional(),
4998
5007
  project: z5.object({
@@ -5013,7 +5022,8 @@ var BiffoConfigSchema = z5.object({
5013
5022
  schema_path: z5.string().nullable().default(null),
5014
5023
  migrations_path: z5.string().default("services/api/migrations")
5015
5024
  }).default({}),
5016
- modules: ModulesSchema.default({})
5025
+ modules: ModulesSchema.default({}),
5026
+ design_tokens: DesignTokensSchema.optional()
5017
5027
  }).superRefine((config, ctx) => {
5018
5028
  const dns = resolveDnsConfig(config);
5019
5029
  if (dns.mode !== "none" && !dns.domain) {
@@ -6997,6 +7007,9 @@ async function runSiblingCreateCommand(name, options) {
6997
7007
  `
6998
7008
  );
6999
7009
  }
7010
+ async function defaultRelockFrontendDeps(cwd) {
7011
+ await execa("pnpm", ["install", "--lockfile-only"], { cwd });
7012
+ }
7000
7013
  async function runSiblingCreate(github, aws, coreAws, git, config, session, options) {
7001
7014
  const totalSteps = 9;
7002
7015
  const { org, repo } = githubRepo(config);
@@ -7059,7 +7072,8 @@ async function runSiblingCreate(github, aws, coreAws, git, config, session, opti
7059
7072
  cloneUrl,
7060
7073
  config,
7061
7074
  options.coreConfig,
7062
- options.githubToken
7075
+ options.githubToken,
7076
+ options.relockFrontendDeps ?? defaultRelockFrontendDeps
7063
7077
  );
7064
7078
  }
7065
7079
  markSiblingStepComplete(session, "push_skeleton");
@@ -7219,7 +7233,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
7219
7233
  }
7220
7234
  return coreIdentity;
7221
7235
  }
7222
- async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
7236
+ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken, relockFrontendDeps) {
7223
7237
  const workDir = mkdtempSync4(join24(tmpdir4(), `biffo-sibling-${config.project.name}-`));
7224
7238
  try {
7225
7239
  writeSiblingTemplate(skeletonRoot, workDir, config, {
@@ -7228,8 +7242,12 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
7228
7242
  // The core template's version at scaffold time (issue #567) — visibility
7229
7243
  // only, mirroring how `biffo init` stamps `biffo.core.json`. See the
7230
7244
  // `template_version` field doc on `SiblingMarker` (lib/sibling-teardown.ts).
7231
- templateVersion: getLatestCoreVersion()
7245
+ templateVersion: getLatestCoreVersion(),
7246
+ designTokens: coreConfig.design_tokens
7232
7247
  });
7248
+ if (coreConfig.design_tokens) {
7249
+ await relockFrontendDeps(join24(workDir, "apps", "frontend"));
7250
+ }
7233
7251
  await git.init(workDir, "dev");
7234
7252
  await git.addRemote(workDir, "origin", cloneUrl);
7235
7253
  await git.add(workDir, ["."]);
@@ -7282,6 +7300,53 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
7282
7300
  } catch (err) {
7283
7301
  if (err.code !== "ENOENT") throw err;
7284
7302
  }
7303
+ if (context.designTokens) {
7304
+ rewriteDesignTokens(targetDir, context.designTokens);
7305
+ }
7306
+ }
7307
+ function rewriteDesignTokens(targetDir, tokens) {
7308
+ const importSpecifier = `${tokens.package}/${tokens.path}`;
7309
+ const packageJsonPath = join24(targetDir, "apps", "frontend", "package.json");
7310
+ try {
7311
+ const pkg = JSON.parse(readFileSync17(packageJsonPath, "utf8"));
7312
+ pkg.dependencies = { ...pkg.dependencies, [tokens.package]: tokens.version };
7313
+ writeFileSync7(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n");
7314
+ } catch (err) {
7315
+ if (err.code !== "ENOENT") throw err;
7316
+ }
7317
+ const globalsCssPath = join24(targetDir, "apps", "frontend", "src", "app", "globals.css");
7318
+ try {
7319
+ const css = readFileSync17(globalsCssPath, "utf8").replace(
7320
+ /^@import '@biffo\/design-tokens\/tokens\.css';$/m,
7321
+ `@import '${importSpecifier}';`
7322
+ );
7323
+ writeFileSync7(globalsCssPath, css);
7324
+ } catch (err) {
7325
+ if (err.code !== "ENOENT") throw err;
7326
+ }
7327
+ const ciYmlPath = join24(targetDir, ".github", "workflows", "ci.yml");
7328
+ try {
7329
+ let yml = readFileSync17(ciYmlPath, "utf8");
7330
+ yml = yml.replace(
7331
+ /^(\s*run: pnpm exec biffo-scale-guard)$/m,
7332
+ `$1 --tokens node_modules/${importSpecifier}`
7333
+ );
7334
+ yml = yml.replace(
7335
+ " timeout-minutes: 20\n defaults:\n run:\n working-directory: apps/frontend\n steps:",
7336
+ " timeout-minutes: 20\n permissions:\n contents: read\n packages: read\n defaults:\n run:\n working-directory: apps/frontend\n steps:"
7337
+ );
7338
+ yml = yml.replace(
7339
+ " continue-on-error: true\n defaults:\n run:\n working-directory: apps/frontend\n steps:",
7340
+ " continue-on-error: true\n permissions:\n contents: read\n packages: read\n defaults:\n run:\n working-directory: apps/frontend\n steps:"
7341
+ );
7342
+ yml = yml.replace(
7343
+ /( {6}- run: pnpm install --frozen-lockfile)\n(?! {8}env:)/g,
7344
+ "$1\n env:\n NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
7345
+ );
7346
+ writeFileSync7(ciYmlPath, yml);
7347
+ } catch (err) {
7348
+ if (err.code !== "ENOENT") throw err;
7349
+ }
7285
7350
  }
7286
7351
  async function configureSiblingGithub(github, config, coreConfig, session, coreIdentity) {
7287
7352
  const { org, repo } = githubRepo(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.303.4",
3
+ "version": "0.304.1",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/verify.sh CHANGED
@@ -1630,6 +1630,118 @@ if ci_has "gitleaks"; then
1630
1630
  fi
1631
1631
  fi
1632
1632
 
1633
+ # Resolve the exact `pnpm` invocation every JS check below runs through
1634
+ # (biffo-template#1920).
1635
+ #
1636
+ # ## The defect
1637
+ #
1638
+ # Modern `pnpm` self-manages its own version: it reads the nearest ancestor
1639
+ # `package.json`'s `packageManager` field and, if the field pins a version
1640
+ # other than the binary currently running, re-execs that pinned version
1641
+ # instead (no `corepack enable` required -- this is `pnpm`'s own built-in
1642
+ # behaviour, confirmed by direct experiment against the actual binary this
1643
+ # machine has installed, not by reading its source). That resolution walks
1644
+ # UP from the invoking process's cwd, so it silently stops applying the
1645
+ # moment a check's working directory has no ancestor `package.json`
1646
+ # declaring the field at all -- which is exactly what
1647
+ # `cli/src/lib/verify-toolchain-missing.test.ts`'s own fixtures are: bare,
1648
+ # isolated tmp git repos with a minimal `package.json` (deliberately, to
1649
+ # simulate a fresh worktree before `pnpm install`) that carries no
1650
+ # `packageManager` field. On a machine whose globally-installed `pnpm`
1651
+ # binary is any OTHER version -- observed: v12.3.4, against this repo's own
1652
+ # pin of v9.15.9 -- the fixture's checks silently run under that OTHER
1653
+ # version instead.
1654
+ #
1655
+ # `run_check_js`'s own INCONCLUSIVE/FAILED split below is a grep against
1656
+ # wording pnpm 9.15.9 specifically prints for a missing toolchain
1657
+ # (`node_modules missing, did you mean to install?`), which a different
1658
+ # major does not necessarily print at all: confirmed live, pnpm 12.3.4 skips
1659
+ # that WARN entirely for the identical missing-toolchain scenario, so the
1660
+ # fixture's real "binary not found" failure reads as an ordinary FAILED
1661
+ # instead of INCONCLUSIVE -- not because detection is wrong, but because two
1662
+ # different pnpm versions produced two different diagnoses of the same
1663
+ # state, and only one of them was ever read.
1664
+ #
1665
+ # ## The fix
1666
+ #
1667
+ # Make the version actually executed impossible to disagree with the
1668
+ # version the detection regex was written against, rather than occasionally
1669
+ # undetected: every `pnpm run`/`pnpm --dir` call in this file is eligible to
1670
+ # be pinned to the EXACT version PINNED IN THE REPO THIS COPY OF verify.sh
1671
+ # LIVES IN, via `corepack`, which forces a specific `pnpm@<version>`
1672
+ # regardless of the PATH order on the invoking machine or of the checked
1673
+ # directory's own `package.json`. Node ships `corepack` alongside itself
1674
+ # from Node 16.9 onward -- this repo's own `engines.node` requires >=22 --
1675
+ # so it is resolved relative to `node` rather than assumed to already be on
1676
+ # PATH (`corepack enable` symlinks it onto PATH but is not required for
1677
+ # `corepack pnpm@<version> ...` to work directly). "Eligible": the pin is
1678
+ # only actually forced when bare `pnpm` would not already resolve to it
1679
+ # unassisted -- see the ambient-version check below for why forcing it
1680
+ # unconditionally is itself a regression, not just unnecessary.
1681
+ #
1682
+ # Deliberately NOT `package.json` in `$PWD`: every real invocation happens
1683
+ # with cwd already at the repo root (`.githooks/pre-push` itself `cd`s there
1684
+ # first), so cwd and "the repo this script belongs to" are the same
1685
+ # directory on every real path -- but the toolchain-missing fixtures above
1686
+ # invoke this exact file with cwd pointed at an isolated, unrelated tmp repo
1687
+ # on purpose, to simulate a fresh worktree of SOME OTHER project before
1688
+ # `pnpm install`. That target has no reason to carry (and must not need to
1689
+ # carry) this repo's own pnpm pin merely to be checked correctly, so the pin
1690
+ # is read from where `$0` says this script actually lives, not from
1691
+ # whatever happens to be checked.
1692
+ PNPM_RUN="pnpm"
1693
+ if [ -z "$LIST" ]; then
1694
+ _verify_script_dir=$(cd "$(dirname "$0")" 2>/dev/null && pwd) || _verify_script_dir=""
1695
+ _verify_repo_root=""
1696
+ [ -n "$_verify_script_dir" ] && _verify_repo_root="$_verify_script_dir/.."
1697
+ if [ -z "$_verify_repo_root" ] || [ ! -f "$_verify_repo_root/package.json" ]; then
1698
+ # Could not resolve $0 to a real path (e.g. sourced from stdin) -- cwd is
1699
+ # the only fallback left, and matches every real invocation anyway.
1700
+ _verify_repo_root="."
1701
+ fi
1702
+ if [ -f "$_verify_repo_root/package.json" ]; then
1703
+ _pnpm_pin=$(grep -oE '"packageManager"[[:space:]]*:[[:space:]]*"pnpm@[0-9][^"]*"' "$_verify_repo_root/package.json" |
1704
+ sed -E 's/.*"pnpm@([^"]*)".*/\1/')
1705
+ # Ask bare `pnpm` what it would already resolve to from $PWD before ever
1706
+ # reaching for corepack. On every REAL check this already equals
1707
+ # `$_pnpm_pin` -- self-management (above) finds this repo's own pin by
1708
+ # walking up from cwd, which is always the repo root -- and in that case
1709
+ # `PNPM_RUN` is deliberately left as plain `pnpm`. This matters beyond
1710
+ # tidiness: `pnpm run test` fans out through turbo, which shells back out
1711
+ # to a bare `pnpm` of its own for each workspace package, and pnpm
1712
+ # refuses to self-manage a SECOND time once corepack has already forced
1713
+ # a version once in the process tree (`ERR_PNPM_BAD_PM_VERSION`,
1714
+ # confirmed live -- ordinary `pnpm run test` segfaulted under a
1715
+ # corepack-wrapped outer call). Reaching for corepack is therefore
1716
+ # reserved for the one case it actually fixes: bare `pnpm` disagreeing
1717
+ # with our pin because nothing above cwd declares one at all (the
1718
+ # toolchain-missing fixtures' isolated tmp repos) -- never the ordinary
1719
+ # path where self-management was already correct on its own.
1720
+ if [ -n "$_pnpm_pin" ] && [ "$(pnpm --version 2>/dev/null)" != "$_pnpm_pin" ]; then
1721
+ _corepack=$(command -v corepack 2>/dev/null)
1722
+ if [ -z "$_corepack" ]; then
1723
+ _node_path=$(command -v node 2>/dev/null)
1724
+ if [ -n "$_node_path" ]; then
1725
+ # `node` on PATH is routinely a symlink into a version manager's
1726
+ # real install directory (nvm, a `~/.local/bin` shim, etc.) --
1727
+ # `corepack` ships as node's OWN sibling in THAT real directory, not
1728
+ # next to wherever the symlink happens to sit, so the link must be
1729
+ # resolved before taking dirname. `readlink -f` is GNU/Linux (every
1730
+ # machine this runs on -- workstation and `ubuntu-24.04` CI alike);
1731
+ # falling back to the unresolved path costs nothing worse than the
1732
+ # `command -v corepack` miss this branch already handles above.
1733
+ _node_real=$(readlink -f "$_node_path" 2>/dev/null) || _node_real="$_node_path"
1734
+ _node_bin_dir=$(dirname "$_node_real")
1735
+ [ -x "$_node_bin_dir/corepack" ] && _corepack="$_node_bin_dir/corepack"
1736
+ fi
1737
+ fi
1738
+ if [ -n "$_corepack" ] && "$_corepack" "pnpm@$_pnpm_pin" --version >/dev/null 2>&1; then
1739
+ PNPM_RUN="$_corepack pnpm@$_pnpm_pin"
1740
+ fi
1741
+ fi
1742
+ fi
1743
+ fi
1744
+
1633
1745
  # JS, cheapest first; `test` last because it is slowest and the most likely to
1634
1746
  # be interrupted by an impatient reader.
1635
1747
  JS_DIRS=$(js_dirs)
@@ -1646,9 +1758,13 @@ if [ -n "$JS_DIRS" ]; then
1646
1758
  label="$(printf '%s' "$s" | tr -d ':')$suffix"
1647
1759
  if have_script "$s" "$d"; then
1648
1760
  if [ "$d" = "." ]; then
1649
- run_check_js "$label" pnpm run "$s"
1761
+ # shellcheck disable=SC2086 # $PNPM_RUN is a deliberately unquoted,
1762
+ # internally-computed word list (corepack + pnpm@<version>, or the
1763
+ # single word "pnpm") -- never external input.
1764
+ run_check_js "$label" $PNPM_RUN run "$s"
1650
1765
  else
1651
- run_check_js "$label" pnpm --dir "$d" run "$s"
1766
+ # shellcheck disable=SC2086 # see above
1767
+ run_check_js "$label" $PNPM_RUN --dir "$d" run "$s"
1652
1768
  fi
1653
1769
  else
1654
1770
  skip "$label" "no \"$s\" script"