@biffo/cli 0.303.3 → 0.304.0

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.
@@ -137,7 +137,7 @@ repository with its own independent `uv sync` / `uv.lock`, not inside
137
137
  biffo-template's workspace. Once copied out, `uv sync && uv run pytest`
138
138
  works standalone with no dependency on the rest of biffo-template.
139
139
 
140
- ## The `biffo-plugin-sdk` dependency: PyPI pin, pending the first release
140
+ ## The `biffo-plugin-sdk` dependency: PyPI pin
141
141
 
142
142
  `pyproject.toml` declares:
143
143
 
@@ -148,41 +148,30 @@ dependencies = [
148
148
  ]
149
149
  ```
150
150
 
151
- This is a **PyPI-style version pin**, and it is the correct end state: the
152
- SDK is versioned `1.0.0` and biffo-template's
151
+ This is a **PyPI-style version pin**: the SDK is published via
152
+ biffo-template's
153
153
  [`.github/workflows/publish-sdk.yml`](https://github.com/keiranholloway/biffo-template/blob/main/.github/workflows/publish-sdk.yml)
154
- builds and publishes it to PyPI (via Trusted Publishing) on a pushed
155
- `sdk-v*` tag. `>=1.0,<2.0` matches the `"biffo-plugin-sdk": "^1.0"` that
154
+ (Trusted Publishing) on a pushed `sdk-v*` tag, and the first release has
155
+ already happened — this skeleton's own `uv.lock` resolves `biffo-plugin-sdk`
156
+ 1.4.0 from `https://pypi.org/simple` (see its `biffo-plugin-sdk` entry).
157
+ `>=1.0,<2.0` matches the `"biffo-plugin-sdk": "^1.0"` that
156
158
  `biffo.plugin.json` declares, and the SDK carries its own independent
157
159
  semver — it is **not** tied to the template's core version, so a major
158
160
  bump here means the plugin API broke and nothing else.
159
161
 
160
- **Ordering caveat.** The release _pipeline_ exists; the _release_ does not
161
- yet. `biffo-plugin-sdk` has never been uploaded — the PyPI project is
162
- unregistered until the owner configures the Trusted Publisher and pushes
163
- `sdk-v1.0.0`. Until that happens, `uv sync` in a freshly-copied plugin repo
164
- still cannot resolve this dependency, and you need one of the two local
165
- overrides below. Once 1.0.0 is live, **delete the override** — the
166
- `dependencies` entry above already points at the real thing.
167
-
168
- Two ways to make local development work before that happens:
169
-
170
- 1. **Path dependency** (if developing inside a biffo-template checkout,
171
- e.g. for a plugin you plan to upstream into `services/`): add
172
- ```toml
173
- [tool.uv.sources]
174
- biffo-plugin-sdk = { path = "../../packages/python-sdk", editable = true }
175
- ```
176
- 2. **Git dependency** (developing this plugin as a genuinely separate repo
177
- against an unpublished SDK):
178
- ```toml
179
- [tool.uv.sources]
180
- biffo-plugin-sdk = { git = "https://github.com/keiranholloway/biffo-template", subdirectory = "packages/python-sdk" }
181
- ```
182
-
183
- Either override goes in `[tool.uv.sources]` only — the PyPI-style
184
- `dependencies` entry above stays as-is, so removing the override is the
185
- only change needed once the SDK actually ships to PyPI.
162
+ `uv sync` in a freshly-copied plugin repo resolves this pin normally; no
163
+ `[tool.uv.sources]` override is needed. If you are developing this plugin
164
+ *inside* a `biffo-template` checkout (e.g. one you plan to upstream into
165
+ `services/`) and want to iterate against an unreleased SDK change before it
166
+ ships, a local override still works:
167
+
168
+ ```toml
169
+ [tool.uv.sources]
170
+ biffo-plugin-sdk = { path = "../../packages/python-sdk", editable = true }
171
+ ```
172
+
173
+ Keep it local (uncommitted, or removed before committing) — the PyPI-style
174
+ `dependencies` entry above is what every scaffolded plugin repo ships with.
186
175
 
187
176
  ## Manifest validation: why CI doesn't hard-gate on `registry-schema.json`
188
177
 
@@ -4,22 +4,14 @@ version = "0.1.0"
4
4
  description = "Example Biffo plugin — replace this with your own plugin's name and description"
5
5
  requires-python = ">=3.13"
6
6
  dependencies = [
7
- # biffo-plugin-sdk has a real release path as of biffo-template's
8
- # .github/workflows/publish-sdk.yml: the package is versioned 1.0.0 and
9
- # publishes to PyPI (via Trusted Publishing) when an `sdk-v*` tag is
10
- # pushed. This pin is therefore the *correct end state* and is left as-is.
11
- #
12
- # ORDERING: the pin only resolves once the owner has actually pushed
13
- # `sdk-v1.0.0` and PyPI has the release. Until that first publish,
14
- # `uv sync` in a freshly-copied plugin repo cannot resolve it — the same
15
- # as before this comment was written, so nothing regressed here; what
16
- # changed is that the fix is now one tag push away rather than unbuilt.
17
- # Deliberately NOT worked around with a git/path source in the committed
18
- # file: baking one in would mean every plugin repo forked from this
19
- # skeleton silently keeps building against a moving git ref long after
20
- # the real release exists. Use a local `[tool.uv.sources]` override until
21
- # then — see the README's "biffo-plugin-sdk dependency" section, which
22
- # documents both options and says which to delete once 1.0.0 is live.
7
+ # biffo-plugin-sdk has a real release path via biffo-template's
8
+ # .github/workflows/publish-sdk.yml: it publishes to PyPI (via Trusted
9
+ # Publishing) when an `sdk-v*` tag is pushed. That first publish has
10
+ # already happened — this skeleton's own uv.lock resolves biffo-plugin-sdk
11
+ # 1.4.0 from https://pypi.org/simple (see uv.lock's own `biffo-plugin-sdk`
12
+ # entry) — so `uv sync` in a freshly-copied plugin repo resolves this pin
13
+ # normally, with no `[tool.uv.sources]` override needed (biffo-template#1769
14
+ # side finding).
23
15
  "biffo-plugin-sdk>=1.0,<2.0",
24
16
  "aws-lambda-powertools[tracer]>=3.4.0",
25
17
  "httpx>=0.28.1",
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);
@@ -8273,7 +8338,8 @@ import {
8273
8338
  import { dirname as dirname7, join as join26 } from "path";
8274
8339
  var STANDALONE_ONLY_ENTRIES = {
8275
8340
  ".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
8276
- "registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
8341
+ "registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin",
8342
+ "uv.lock": "a standalone repo's own lockfile, substituted like any other text file (issue #1769) \u2014 a nested lock inside a monorepo's services/ would be wrong there, since the host resolves its own dependency tree"
8277
8343
  };
8278
8344
  var NEVER_COPY = /* @__PURE__ */ new Set([
8279
8345
  ".git",
@@ -8284,7 +8350,6 @@ var NEVER_COPY = /* @__PURE__ */ new Set([
8284
8350
  ".pytest_cache",
8285
8351
  ".mypy_cache",
8286
8352
  "dist",
8287
- "uv.lock",
8288
8353
  ".DS_Store"
8289
8354
  ]);
8290
8355
  var PLUGIN_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
@@ -8537,6 +8602,11 @@ async function runStandaloneCreate(names, options, deps) {
8537
8602
  `
8538
8603
  );
8539
8604
  if (options.commit) {
8605
+ if (!existsSync27(join27(destDir, "uv.lock"))) {
8606
+ throw new Error(
8607
+ `Scaffolded plugin at ${destDir} has no uv.lock. The standalone layout must carry a substituted uv.lock (see plugin-scaffold.ts STANDALONE_ONLY_ENTRIES) so the first commit's CI run can satisfy 'uv sync --locked'. Refusing to commit a repo that would ship red on its very first push (issue #1769) \u2014 run 'uv lock' in ${destDir} first.`
8608
+ );
8609
+ }
8540
8610
  await deps.git.init(destDir);
8541
8611
  await deps.git.add(destDir, ["."]);
8542
8612
  await deps.git.commit(destDir, `feat: scaffold ${names.slug} plugin`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.303.3",
3
+ "version": "0.304.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",