@biffo/cli 0.39.0 → 0.41.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.
Files changed (73) hide show
  1. package/_skeletons/plugin-template/.github/workflows/ci.yml +124 -0
  2. package/_skeletons/plugin-template/.github/workflows/release.yml +114 -0
  3. package/_skeletons/plugin-template/README.md +232 -0
  4. package/_skeletons/plugin-template/biffo.plugin.json +89 -0
  5. package/_skeletons/plugin-template/pyproject.toml +100 -0
  6. package/_skeletons/plugin-template/registry-schema.json +294 -0
  7. package/_skeletons/plugin-template/src/__init__.py +0 -0
  8. package/_skeletons/plugin-template/src/example_plugin/__init__.py +10 -0
  9. package/_skeletons/plugin-template/src/example_plugin/main.py +63 -0
  10. package/_skeletons/plugin-template/src/example_plugin/manifest.py +30 -0
  11. package/_skeletons/plugin-template/src/example_plugin/plugin.py +92 -0
  12. package/_skeletons/plugin-template/terraform/README.md +146 -0
  13. package/_skeletons/plugin-template/terraform/main.tf +167 -0
  14. package/_skeletons/plugin-template/terraform/outputs.tf +33 -0
  15. package/_skeletons/plugin-template/terraform/variables.tf +133 -0
  16. package/_skeletons/plugin-template/tests/conftest.py +17 -0
  17. package/_skeletons/plugin-template/tests/fakes.py +74 -0
  18. package/_skeletons/plugin-template/tests/test_example_plugin.py +108 -0
  19. package/_skeletons/registry/README.md +27 -0
  20. package/_skeletons/registry/plugins.json +5 -0
  21. package/_skeletons/registry/registry-schema.json +294 -0
  22. package/_skeletons/sibling-template/.github/renovate.json +38 -0
  23. package/_skeletons/sibling-template/.github/workflows/ci.yml +185 -0
  24. package/_skeletons/sibling-template/.github/workflows/codeql.yml +56 -0
  25. package/_skeletons/sibling-template/.github/workflows/deploy.yml +219 -0
  26. package/_skeletons/sibling-template/.github/workflows/destroy-infra.yml +73 -0
  27. package/_skeletons/sibling-template/README.md +164 -0
  28. package/_skeletons/sibling-template/_gitignore +71 -0
  29. package/_skeletons/sibling-template/apps/frontend/.env.example +14 -0
  30. package/_skeletons/sibling-template/apps/frontend/eslint.config.mjs +10 -0
  31. package/_skeletons/sibling-template/apps/frontend/next.config.ts +20 -0
  32. package/_skeletons/sibling-template/apps/frontend/package.json +42 -0
  33. package/_skeletons/sibling-template/apps/frontend/pnpm-lock.yaml +5268 -0
  34. package/_skeletons/sibling-template/apps/frontend/pnpm-workspace.yaml +19 -0
  35. package/_skeletons/sibling-template/apps/frontend/src/app/globals.css +34 -0
  36. package/_skeletons/sibling-template/apps/frontend/src/app/layout.tsx +15 -0
  37. package/_skeletons/sibling-template/apps/frontend/src/app/page.test.tsx +75 -0
  38. package/_skeletons/sibling-template/apps/frontend/src/app/page.tsx +85 -0
  39. package/_skeletons/sibling-template/apps/frontend/src/lib/api-client.ts +58 -0
  40. package/_skeletons/sibling-template/apps/frontend/src/lib/auth.test.ts +135 -0
  41. package/_skeletons/sibling-template/apps/frontend/src/lib/auth.ts +87 -0
  42. package/_skeletons/sibling-template/apps/frontend/src/test-setup.ts +1 -0
  43. package/_skeletons/sibling-template/apps/frontend/tsconfig.json +29 -0
  44. package/_skeletons/sibling-template/apps/frontend/vitest.config.ts +18 -0
  45. package/_skeletons/sibling-template/biffo.sibling.json +7 -0
  46. package/_skeletons/sibling-template/infra/backend.tf +23 -0
  47. package/_skeletons/sibling-template/infra/main.tf +79 -0
  48. package/_skeletons/sibling-template/infra/outputs.tf +17 -0
  49. package/_skeletons/sibling-template/infra/variables.tf +63 -0
  50. package/_skeletons/sibling-template/modules/cloud/aws/api-gateway/main.tf +114 -0
  51. package/_skeletons/sibling-template/modules/cloud/aws/api-gateway/outputs.tf +12 -0
  52. package/_skeletons/sibling-template/modules/cloud/aws/api-gateway/variables.tf +41 -0
  53. package/_skeletons/sibling-template/modules/cloud/aws/compute/main.tf +157 -0
  54. package/_skeletons/sibling-template/modules/cloud/aws/compute/outputs.tf +4 -0
  55. package/_skeletons/sibling-template/modules/cloud/aws/compute/variables.tf +70 -0
  56. package/_skeletons/sibling-template/modules/cloud/aws/storage/main.tf +78 -0
  57. package/_skeletons/sibling-template/modules/cloud/aws/storage/outputs.tf +4 -0
  58. package/_skeletons/sibling-template/modules/cloud/aws/storage/variables.tf +12 -0
  59. package/_skeletons/sibling-template/services/api/pyproject.toml +69 -0
  60. package/_skeletons/sibling-template/services/api/src/api/__init__.py +0 -0
  61. package/_skeletons/sibling-template/services/api/src/api/config.py +31 -0
  62. package/_skeletons/sibling-template/services/api/src/api/core_client.py +64 -0
  63. package/_skeletons/sibling-template/services/api/src/api/main.py +43 -0
  64. package/_skeletons/sibling-template/services/api/src/api/middleware/__init__.py +0 -0
  65. package/_skeletons/sibling-template/services/api/src/api/middleware/auth.py +111 -0
  66. package/_skeletons/sibling-template/services/api/src/api/routers/__init__.py +0 -0
  67. package/_skeletons/sibling-template/services/api/src/api/routers/whoami.py +21 -0
  68. package/_skeletons/sibling-template/services/api/tests/conftest.py +24 -0
  69. package/_skeletons/sibling-template/services/api/tests/test_whoami.py +21 -0
  70. package/_skeletons/sibling-template/services/api/uv.lock +1160 -0
  71. package/core.version +1 -1
  72. package/dist/index.js +117 -88
  73. package/package.json +5 -4
package/core.version CHANGED
@@ -1 +1 @@
1
- 0.39.0
1
+ 0.41.1
package/dist/index.js CHANGED
@@ -3557,40 +3557,61 @@ var SiblingConfigSchema = z4.object({
3557
3557
  // src/commands/sibling-create.ts
3558
3558
  import { cpSync as cpSync2, existsSync as existsSync14, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
3559
3559
  import { tmpdir as tmpdir4 } from "os";
3560
- import { dirname as dirname6, join as join15, resolve as resolve9 } from "path";
3560
+ import { dirname as dirname6, join as join16, resolve as resolve9 } from "path";
3561
+ import { fileURLToPath as fileURLToPath4 } from "url";
3561
3562
  import chalk11 from "chalk";
3562
3563
  import { Command as Command11 } from "commander";
3563
3564
 
3565
+ // src/lib/skeleton-dotfiles.ts
3566
+ import { readdirSync as readdirSync7, renameSync } from "fs";
3567
+ import { join as join14 } from "path";
3568
+ var PACKAGED_GITIGNORE = "_gitignore";
3569
+ var REAL_GITIGNORE = ".gitignore";
3570
+ function restorePackagedDotfiles(dir) {
3571
+ const restored = [];
3572
+ for (const entry of readdirSync7(dir, { withFileTypes: true })) {
3573
+ const full = join14(dir, entry.name);
3574
+ if (entry.isDirectory()) {
3575
+ restored.push(...restorePackagedDotfiles(full));
3576
+ } else if (entry.name === PACKAGED_GITIGNORE) {
3577
+ const target = join14(dir, REAL_GITIGNORE);
3578
+ renameSync(full, target);
3579
+ restored.push(target);
3580
+ }
3581
+ }
3582
+ return restored;
3583
+ }
3584
+
3564
3585
  // src/lib/sibling-session.ts
3565
3586
  import {
3566
3587
  existsSync as existsSync13,
3567
3588
  mkdirSync as mkdirSync5,
3568
- readdirSync as readdirSync7,
3589
+ readdirSync as readdirSync8,
3569
3590
  readFileSync as readFileSync10,
3570
3591
  rmSync as rmSync5,
3571
3592
  statSync as statSync4,
3572
3593
  writeFileSync as writeFileSync5
3573
3594
  } from "fs";
3574
3595
  import { homedir as homedir3 } from "os";
3575
- import { join as join14 } from "path";
3596
+ import { join as join15 } from "path";
3576
3597
  function sessionsDir2() {
3577
- return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join14(homedir3(), ".biffo", "sibling-sessions");
3598
+ return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join15(homedir3(), ".biffo", "sibling-sessions");
3578
3599
  }
3579
3600
  function sessionPath2(projectName) {
3580
- return join14(sessionsDir2(), `${projectName}.json`);
3601
+ return join15(sessionsDir2(), `${projectName}.json`);
3581
3602
  }
3582
3603
  function findLatestSiblingSession() {
3583
3604
  const dir = sessionsDir2();
3584
3605
  if (!existsSync13(dir)) return null;
3585
- const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
3606
+ const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
3586
3607
  if (files.length === 0) return null;
3587
3608
  const sorted = files.map((f) => {
3588
- const fullPath = join14(dir, f);
3609
+ const fullPath = join15(dir, f);
3589
3610
  const mtime = existsSync13(fullPath) ? statSync4(fullPath).mtimeMs : -1;
3590
3611
  return { f, mtime };
3591
3612
  }).sort((a, b) => b.mtime - a.mtime);
3592
3613
  try {
3593
- return JSON.parse(readFileSync10(join14(dir, sorted[0].f), "utf8"));
3614
+ return JSON.parse(readFileSync10(join15(dir, sorted[0].f), "utf8"));
3594
3615
  } catch {
3595
3616
  return null;
3596
3617
  }
@@ -3895,7 +3916,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
3895
3916
  return coreIdentity;
3896
3917
  }
3897
3918
  async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
3898
- const workDir = mkdtempSync4(join15(tmpdir4(), `biffo-sibling-${config.project.name}-`));
3919
+ const workDir = mkdtempSync4(join16(tmpdir4(), `biffo-sibling-${config.project.name}-`));
3899
3920
  try {
3900
3921
  writeSiblingTemplate(skeletonRoot, workDir, config, {
3901
3922
  coreProjectName: coreConfig.project.name,
@@ -3915,8 +3936,9 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
3915
3936
  throw new Error(`Sibling template not found at ${templateRoot}`);
3916
3937
  }
3917
3938
  cpSync2(templateRoot, targetDir, { recursive: true });
3939
+ restorePackagedDotfiles(targetDir);
3918
3940
  writeFileSync6(
3919
- join15(targetDir, "biffo.sibling.json"),
3941
+ join16(targetDir, "biffo.sibling.json"),
3920
3942
  JSON.stringify(
3921
3943
  {
3922
3944
  name: config.project.name,
@@ -3932,7 +3954,7 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
3932
3954
  2
3933
3955
  ) + "\n"
3934
3956
  );
3935
- const envPath = join15(targetDir, "apps", "frontend", ".env.example");
3957
+ const envPath = join16(targetDir, "apps", "frontend", ".env.example");
3936
3958
  try {
3937
3959
  const path = basePathFor(context.pathPrefix);
3938
3960
  const content = readFileSync11(envPath, "utf8").replace(/^NEXT_PUBLIC_SIBLING_NAME=.*$/m, `NEXT_PUBLIC_SIBLING_NAME=${config.project.name}`).replace(/^NEXT_PUBLIC_SIBLING_PATH_PREFIX=.*$/m, `NEXT_PUBLIC_SIBLING_PATH_PREFIX=${path}`).replace(/^NEXT_PUBLIC_BASE_PATH=.*$/m, `NEXT_PUBLIC_BASE_PATH=${path}`);
@@ -3950,7 +3972,10 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
3950
3972
  await github.createEnvironments(config);
3951
3973
  await github.enableVulnerabilityAlerts(org, repo);
3952
3974
  await github.setRepoVariable(org, repo, "PROJECT_NAME", config.project.name);
3953
- await github.setRepoVariable(org, repo, "PATH_PREFIX", resolvePathPrefix(config));
3975
+ const pathPrefix = resolvePathPrefix(config);
3976
+ if (pathPrefix !== "") {
3977
+ await github.setRepoVariable(org, repo, "PATH_PREFIX", pathPrefix);
3978
+ }
3954
3979
  await github.setRepoVariable(org, repo, "AWS_REGION", awsConfig(config).region);
3955
3980
  await github.setRepoVariable(org, repo, "SIBLING_DEPLOY_ENABLED", "true");
3956
3981
  try {
@@ -4001,7 +4026,7 @@ function readExistingSiblingOrigins(filePath) {
4001
4026
  }
4002
4027
  }
4003
4028
  function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
4004
- const cdnVarsPath = join15(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
4029
+ const cdnVarsPath = join16(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
4005
4030
  let declaresSiblingOrigins = false;
4006
4031
  try {
4007
4032
  declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync11(cdnVarsPath, "utf8"));
@@ -4014,7 +4039,7 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
4014
4039
  );
4015
4040
  }
4016
4041
  if (!isRootPathPrefix(pathPrefix)) return;
4017
- const cdnMainPath = join15(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
4042
+ const cdnMainPath = join16(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
4018
4043
  let supportsRoot = false;
4019
4044
  try {
4020
4045
  supportsRoot = /root_sibling_registered/.test(readFileSync11(cdnMainPath, "utf8"));
@@ -4047,8 +4072,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
4047
4072
  for (const env of config.environments) {
4048
4073
  const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
4049
4074
  const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
4050
- const relativePath = join15("infra", "environments", env, "siblings.auto.tfvars.json");
4051
- const filePath = join15(cloneDir, relativePath);
4075
+ const relativePath = join16("infra", "environments", env, "siblings.auto.tfvars.json");
4076
+ const filePath = join16(cloneDir, relativePath);
4052
4077
  const existing = readExistingSiblingOrigins(filePath);
4053
4078
  const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
4054
4079
  name,
@@ -4125,15 +4150,18 @@ function awsConfig(config) {
4125
4150
  return config.cloud.config;
4126
4151
  }
4127
4152
  function defaultSiblingTemplateRoot() {
4128
- let dir = dirname6(new URL(import.meta.url).pathname);
4153
+ const start = dirname6(fileURLToPath4(import.meta.url));
4154
+ let dir = start;
4129
4155
  for (; ; ) {
4130
- const candidate = join15(dir, "_skeletons", "sibling-template");
4156
+ const candidate = join16(dir, "_skeletons", "sibling-template");
4131
4157
  if (existsSync14(candidate)) return candidate;
4132
4158
  const parent = dirname6(dir);
4133
4159
  if (parent === dir) break;
4134
4160
  dir = parent;
4135
4161
  }
4136
- return resolve9(process.cwd(), "_skeletons", "sibling-template");
4162
+ throw new Error(
4163
+ `Could not locate _skeletons/sibling-template above ${start}. This CLI build is missing its sibling skeleton: the published package must ship _skeletons/ beside dist/ (written by prepack, listed in package.json "files"). Reinstall the CLI (npm i -g @biffo/cli@latest), or pass --template <path> to a biffo-template checkout.`
4164
+ );
4137
4165
  }
4138
4166
 
4139
4167
  // src/commands/init.ts
@@ -4557,14 +4585,14 @@ import { Command as Command20 } from "commander";
4557
4585
 
4558
4586
  // src/commands/plugin-create.ts
4559
4587
  import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
4560
- import { dirname as dirname8, join as join18, resolve as resolve11 } from "path";
4561
- import { fileURLToPath as fileURLToPath4 } from "url";
4588
+ import { dirname as dirname8, join as join19, resolve as resolve11 } from "path";
4589
+ import { fileURLToPath as fileURLToPath5 } from "url";
4562
4590
  import chalk13 from "chalk";
4563
4591
  import { Command as Command13 } from "commander";
4564
4592
 
4565
4593
  // src/lib/plugin-locations.ts
4566
- import { existsSync as existsSync15, readdirSync as readdirSync8 } from "fs";
4567
- import { join as join16 } from "path";
4594
+ import { existsSync as existsSync15, readdirSync as readdirSync9 } from "fs";
4595
+ import { join as join17 } from "path";
4568
4596
  var FIRST_PARTY_PLUGINS_DIR = "_plugins";
4569
4597
  var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
4570
4598
  function pluginDir(name, channel) {
@@ -4573,10 +4601,10 @@ function pluginDir(name, channel) {
4573
4601
  function scanDir(absDir, relDir, channel) {
4574
4602
  if (!existsSync15(absDir)) return [];
4575
4603
  const found = [];
4576
- for (const entry of readdirSync8(absDir, { withFileTypes: true })) {
4604
+ for (const entry of readdirSync9(absDir, { withFileTypes: true })) {
4577
4605
  if (!entry.isDirectory()) continue;
4578
4606
  if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
4579
- const manifestPath = join16(absDir, entry.name, PLUGIN_MANIFEST_FILE);
4607
+ const manifestPath = join17(absDir, entry.name, PLUGIN_MANIFEST_FILE);
4580
4608
  if (!existsSync15(manifestPath)) continue;
4581
4609
  found.push({
4582
4610
  dirName: entry.name,
@@ -4588,11 +4616,11 @@ function scanDir(absDir, relDir, channel) {
4588
4616
  return found;
4589
4617
  }
4590
4618
  function findInstalledPlugins(cwd) {
4591
- const servicesDir = join16(cwd, "services");
4619
+ const servicesDir = join17(cwd, "services");
4592
4620
  return [
4593
4621
  ...scanDir(servicesDir, "services", "third-party"),
4594
4622
  ...scanDir(
4595
- join16(servicesDir, FIRST_PARTY_PLUGINS_DIR),
4623
+ join17(servicesDir, FIRST_PARTY_PLUGINS_DIR),
4596
4624
  `services/${FIRST_PARTY_PLUGINS_DIR}`,
4597
4625
  "first-party"
4598
4626
  )
@@ -4755,10 +4783,10 @@ import {
4755
4783
  existsSync as existsSync16,
4756
4784
  mkdirSync as mkdirSync7,
4757
4785
  readFileSync as readFileSync13,
4758
- readdirSync as readdirSync9,
4786
+ readdirSync as readdirSync10,
4759
4787
  writeFileSync as writeFileSync7
4760
4788
  } from "fs";
4761
- import { dirname as dirname7, join as join17 } from "path";
4789
+ import { dirname as dirname7, join as join18 } from "path";
4762
4790
  var STANDALONE_ONLY_ENTRIES = {
4763
4791
  ".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
4764
4792
  "registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
@@ -4814,7 +4842,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4814
4842
  if (!existsSync16(skeletonRoot)) {
4815
4843
  throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
4816
4844
  }
4817
- if (!existsSync16(join17(skeletonRoot, "terraform"))) {
4845
+ if (!existsSync16(join18(skeletonRoot, "terraform"))) {
4818
4846
  throw new Error(
4819
4847
  `Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
4820
4848
  );
@@ -4822,8 +4850,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4822
4850
  const skipped = [];
4823
4851
  const files = [];
4824
4852
  const walk = (relDir) => {
4825
- const absDir = join17(skeletonRoot, relDir);
4826
- for (const entry of readdirSync9(absDir, { withFileTypes: true }).sort(
4853
+ const absDir = join18(skeletonRoot, relDir);
4854
+ for (const entry of readdirSync10(absDir, { withFileTypes: true }).sort(
4827
4855
  (a, b) => a.name.localeCompare(b.name)
4828
4856
  )) {
4829
4857
  if (NEVER_COPY.has(entry.name)) continue;
@@ -4837,14 +4865,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4837
4865
  continue;
4838
4866
  }
4839
4867
  const destRel = applySubstitutions(relPath, names);
4840
- const destPath = join17(destDir, destRel);
4868
+ const destPath = join18(destDir, destRel);
4841
4869
  mkdirSync7(dirname7(destPath), { recursive: true });
4842
4870
  if (BINARY_EXTENSIONS.test(entry.name)) {
4843
- copyFileSync(join17(skeletonRoot, relPath), destPath);
4871
+ copyFileSync(join18(skeletonRoot, relPath), destPath);
4844
4872
  } else {
4845
4873
  writeFileSync7(
4846
4874
  destPath,
4847
- applySubstitutions(readFileSync13(join17(skeletonRoot, relPath), "utf8"), names)
4875
+ applySubstitutions(readFileSync13(join18(skeletonRoot, relPath), "utf8"), names)
4848
4876
  );
4849
4877
  }
4850
4878
  files.push(destRel);
@@ -4861,7 +4889,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4861
4889
  function findSkeletonRoot(startDir, skeleton) {
4862
4890
  let dir = startDir;
4863
4891
  for (; ; ) {
4864
- const candidate = join17(dir, "_skeletons", skeleton);
4892
+ const candidate = join18(dir, "_skeletons", skeleton);
4865
4893
  if (existsSync16(candidate)) return candidate;
4866
4894
  const parent = dirname7(dir);
4867
4895
  if (parent === dir) return null;
@@ -4899,7 +4927,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
4899
4927
  );
4900
4928
  async function runPluginCreate(name, options, deps) {
4901
4929
  const names = deriveNames(name);
4902
- const isInstance = existsSync17(join18(options.cwd, INSTANCE_CORE_FILE));
4930
+ const isInstance = existsSync17(join19(options.cwd, INSTANCE_CORE_FILE));
4903
4931
  if (options.firstParty && isInstance) {
4904
4932
  throw new Error(
4905
4933
  `--first-party scaffolds into services/_plugins/, which is template-owned: \`biffo core upgrade\` three-way-merges it against the template on every upgrade, and the template has no '${names.slug}'. This checkout is a Biffo instance (${INSTANCE_CORE_FILE} is present), so your plugin belongs in the user-owned ${pluginDir(names.slug, "third-party")}/ \u2014 re-run without --first-party.`
@@ -4907,8 +4935,8 @@ async function runPluginCreate(name, options, deps) {
4907
4935
  }
4908
4936
  const channel = options.firstParty ? "first-party" : "third-party";
4909
4937
  const relDir = pluginDir(names.slug, channel);
4910
- const destDir = join18(options.cwd, relDir);
4911
- const servicesDir = join18(options.cwd, "services");
4938
+ const destDir = join19(options.cwd, relDir);
4939
+ const servicesDir = join19(options.cwd, "services");
4912
4940
  if (!existsSync17(servicesDir)) {
4913
4941
  throw new Error(
4914
4942
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
@@ -4917,8 +4945,8 @@ async function runPluginCreate(name, options, deps) {
4917
4945
  if (existsSync17(destDir)) {
4918
4946
  throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
4919
4947
  }
4920
- const here = dirname8(fileURLToPath4(import.meta.url));
4921
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join18(options.cwd, "_skeletons", "plugin-template");
4948
+ const here = dirname8(fileURLToPath5(import.meta.url));
4949
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join19(options.cwd, "_skeletons", "plugin-template");
4922
4950
  if (!existsSync17(skeletonRoot)) {
4923
4951
  throw new Error(
4924
4952
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
@@ -4929,11 +4957,12 @@ async function runPluginCreate(name, options, deps) {
4929
4957
  return;
4930
4958
  }
4931
4959
  const { files, skipped } = scaffoldPlugin(skeletonRoot, destDir, names);
4960
+ restorePackagedDotfiles(destDir);
4932
4961
  log.success(`Scaffolded ${files.length} file(s) into ${relDir}/`);
4933
4962
  for (const { entry, reason } of skipped) {
4934
4963
  log.info(`Skipped ${entry} \u2014 ${reason}`);
4935
4964
  }
4936
- const manifestPath = join18(destDir, "biffo.plugin.json");
4965
+ const manifestPath = join19(destDir, "biffo.plugin.json");
4937
4966
  const manifest = validateManifest(JSON.parse(readFileSync14(manifestPath, "utf8")));
4938
4967
  if (manifest.name !== names.slug) {
4939
4968
  throw new Error(
@@ -5125,13 +5154,13 @@ function printEntry(entry) {
5125
5154
 
5126
5155
  // src/commands/plugin-install.ts
5127
5156
  import { cpSync as cpSync3, existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync16, statSync as statSync5 } from "fs";
5128
- import { basename, join as join21, relative as relative3, resolve as resolve12 } from "path";
5157
+ import { basename, join as join22, relative as relative3, resolve as resolve12 } from "path";
5129
5158
  import chalk15 from "chalk";
5130
5159
  import { Command as Command15 } from "commander";
5131
5160
 
5132
5161
  // src/adapters/plugin-migrations/index.ts
5133
5162
  import { execa as execa3 } from "execa";
5134
- import { join as join19 } from "path";
5163
+ import { join as join20 } from "path";
5135
5164
  var PluginMigrationsAdapter = class {
5136
5165
  /**
5137
5166
  * Generates migration file(s) for `pluginNames` (every discovered
@@ -5140,22 +5169,22 @@ var PluginMigrationsAdapter = class {
5140
5169
  * or declared no tables.
5141
5170
  */
5142
5171
  async generate(cwd, pluginNames) {
5143
- const scriptPath = join19(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
5172
+ const scriptPath = join20(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
5144
5173
  const args = [
5145
5174
  "run",
5146
5175
  "python",
5147
5176
  scriptPath,
5148
5177
  "--services-root",
5149
- join19(cwd, "services"),
5178
+ join20(cwd, "services"),
5150
5179
  "--versions-dir",
5151
- join19(cwd, "services", "api", "migrations", "versions")
5180
+ join20(cwd, "services", "api", "migrations", "versions")
5152
5181
  ];
5153
5182
  for (const name of pluginNames ?? []) {
5154
5183
  args.push("--plugin", name);
5155
5184
  }
5156
5185
  let result;
5157
5186
  try {
5158
- result = await execa3("uv", args, { cwd: join19(cwd, "services", "api") });
5187
+ result = await execa3("uv", args, { cwd: join20(cwd, "services", "api") });
5159
5188
  } catch (err) {
5160
5189
  const cause = err;
5161
5190
  if (cause.code === "ENOENT") {
@@ -5172,8 +5201,8 @@ var PluginMigrationsAdapter = class {
5172
5201
  };
5173
5202
 
5174
5203
  // src/lib/plugin-terraform-wiring.ts
5175
- import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync15, readdirSync as readdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
5176
- import { join as join20 } from "path";
5204
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
5205
+ import { join as join21 } from "path";
5177
5206
  var TEMPLATE_MODULE_DIR = "_template";
5178
5207
  var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
5179
5208
  var GENERATED_TF_FILE = "plugins.generated.tf";
@@ -5191,45 +5220,45 @@ function standardArguments(pluginName, handler) {
5191
5220
  ];
5192
5221
  }
5193
5222
  function listPluginModules(cwd) {
5194
- const dir = join20(cwd, "modules", "plugins");
5223
+ const dir = join21(cwd, "modules", "plugins");
5195
5224
  let entries;
5196
5225
  try {
5197
- entries = readdirSync10(dir, { withFileTypes: true });
5226
+ entries = readdirSync11(dir, { withFileTypes: true });
5198
5227
  } catch {
5199
5228
  return [];
5200
5229
  }
5201
5230
  return entries.filter((e) => e.isDirectory() && e.name !== TEMPLATE_MODULE_DIR && !e.name.startsWith(".")).map((e) => e.name).sort();
5202
5231
  }
5203
5232
  function listEnvironments(cwd) {
5204
- const dir = join20(cwd, "infra", "environments");
5233
+ const dir = join21(cwd, "infra", "environments");
5205
5234
  let entries;
5206
5235
  try {
5207
- entries = readdirSync10(dir, { withFileTypes: true });
5236
+ entries = readdirSync11(dir, { withFileTypes: true });
5208
5237
  } catch {
5209
5238
  return [];
5210
5239
  }
5211
5240
  return entries.filter((e) => {
5212
- if (!e.isDirectory() || !existsSync18(join20(dir, e.name, "main.tf"))) return false;
5213
- return declaredVariables(join20(dir, e.name)).has("enabled_plugins");
5241
+ if (!e.isDirectory() || !existsSync18(join21(dir, e.name, "main.tf"))) return false;
5242
+ return declaredVariables(join21(dir, e.name)).has("enabled_plugins");
5214
5243
  }).map((e) => e.name).sort();
5215
5244
  }
5216
5245
  function listUnwirableEnvironments(cwd) {
5217
- const dir = join20(cwd, "infra", "environments");
5246
+ const dir = join21(cwd, "infra", "environments");
5218
5247
  let entries;
5219
5248
  try {
5220
- entries = readdirSync10(dir, { withFileTypes: true });
5249
+ entries = readdirSync11(dir, { withFileTypes: true });
5221
5250
  } catch {
5222
5251
  return [];
5223
5252
  }
5224
5253
  return entries.filter(
5225
- (e) => e.isDirectory() && existsSync18(join20(dir, e.name, "main.tf")) && !declaredVariables(join20(dir, e.name)).has("enabled_plugins")
5254
+ (e) => e.isDirectory() && existsSync18(join21(dir, e.name, "main.tf")) && !declaredVariables(join21(dir, e.name)).has("enabled_plugins")
5226
5255
  ).map((e) => e.name).sort();
5227
5256
  }
5228
5257
  function declaredVariables(moduleDir) {
5229
5258
  const names = /* @__PURE__ */ new Set();
5230
5259
  let entries;
5231
5260
  try {
5232
- entries = readdirSync10(moduleDir, { withFileTypes: true });
5261
+ entries = readdirSync11(moduleDir, { withFileTypes: true });
5233
5262
  } catch {
5234
5263
  return names;
5235
5264
  }
@@ -5237,7 +5266,7 @@ function declaredVariables(moduleDir) {
5237
5266
  if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
5238
5267
  let contents;
5239
5268
  try {
5240
- contents = readFileSync15(join20(moduleDir, entry.name), "utf8");
5269
+ contents = readFileSync15(join21(moduleDir, entry.name), "utf8");
5241
5270
  } catch {
5242
5271
  continue;
5243
5272
  }
@@ -5309,12 +5338,12 @@ function syncPluginTerraform(cwd) {
5309
5338
  const changedPaths = [];
5310
5339
  const rendered = plugins.map((name) => ({
5311
5340
  name,
5312
- declaredVariables: declaredVariables(join20(cwd, "modules", "plugins", name))
5341
+ declaredVariables: declaredVariables(join21(cwd, "modules", "plugins", name))
5313
5342
  }));
5314
5343
  for (const env of environments) {
5315
- const envDir = join20(cwd, "infra", "environments", env);
5316
- const tfPath = join20(envDir, GENERATED_TF_FILE);
5317
- const tfvarsPath = join20(envDir, GENERATED_TFVARS_FILE);
5344
+ const envDir = join21(cwd, "infra", "environments", env);
5345
+ const tfPath = join21(envDir, GENERATED_TF_FILE);
5346
+ const tfvarsPath = join21(envDir, GENERATED_TFVARS_FILE);
5318
5347
  const relBase = `infra/environments/${env}`;
5319
5348
  if (plugins.length === 0) {
5320
5349
  for (const [abs, rel] of [
@@ -5384,7 +5413,7 @@ function resolveLocalPlugin(localPath) {
5384
5413
  if (!statSync5(localPath).isDirectory()) {
5385
5414
  throw new Error(`--local path is not a directory: ${localPath}`);
5386
5415
  }
5387
- const manifestPath = join21(localPath, "biffo.plugin.json");
5416
+ const manifestPath = join22(localPath, "biffo.plugin.json");
5388
5417
  if (!existsSync19(manifestPath)) {
5389
5418
  throw new Error(
5390
5419
  `${localPath} does not contain a biffo.plugin.json manifest at its root \u2014 is it a plugin directory? (Scaffold one with \`biffo plugin create <name>\`.)`
@@ -5411,7 +5440,7 @@ function parsePluginTarget(target) {
5411
5440
  async function cloneAndValidatePlugin(entry, git) {
5412
5441
  const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
5413
5442
  try {
5414
- const manifestPath = join21(tmpDir, "biffo.plugin.json");
5443
+ const manifestPath = join22(tmpDir, "biffo.plugin.json");
5415
5444
  if (!existsSync19(manifestPath)) {
5416
5445
  throw new Error(
5417
5446
  `Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
@@ -5440,7 +5469,7 @@ async function runPluginInstall(target, options, deps) {
5440
5469
  `Nothing to install. Pass a registry target (e.g. \`biffo plugin install acme-crm@1.0\`) or a local plugin directory (\`biffo plugin install --local services/acme-crm\`).`
5441
5470
  );
5442
5471
  }
5443
- const servicesDir = join21(options.cwd, "services");
5472
+ const servicesDir = join22(options.cwd, "services");
5444
5473
  if (!existsSync19(servicesDir)) {
5445
5474
  throw new Error(
5446
5475
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
@@ -5459,8 +5488,8 @@ async function runPluginInstall(target, options, deps) {
5459
5488
  }
5460
5489
  const pluginName = entry ? entry.name : source.name;
5461
5490
  const relTargetDir = pluginDir(pluginName, "third-party");
5462
- const targetDir = join21(options.cwd, relTargetDir);
5463
- const modulesDir = join21(options.cwd, "modules", "plugins", pluginName);
5491
+ const targetDir = join22(options.cwd, relTargetDir);
5492
+ const modulesDir = join22(options.cwd, "modules", "plugins", pluginName);
5464
5493
  const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
5465
5494
  if (existsSync19(targetDir) && !inTreeSource) {
5466
5495
  throw new Error(
@@ -5505,7 +5534,7 @@ async function runPluginInstall(target, options, deps) {
5505
5534
  log.success(`Installed plugin source at ${relTargetDir}/`);
5506
5535
  }
5507
5536
  const stagePaths = [relTargetDir];
5508
- const tfSourceDir = join21(targetDir, "terraform");
5537
+ const tfSourceDir = join22(targetDir, "terraform");
5509
5538
  if (existsSync19(tfSourceDir)) {
5510
5539
  mkdirSync9(modulesDir, { recursive: true });
5511
5540
  cpSync3(tfSourceDir, modulesDir, { recursive: true });
@@ -5601,7 +5630,7 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
5601
5630
 
5602
5631
  // src/commands/plugin-list.ts
5603
5632
  import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
5604
- import { join as join22, resolve as resolve13 } from "path";
5633
+ import { join as join23, resolve as resolve13 } from "path";
5605
5634
  import chalk16 from "chalk";
5606
5635
  import { Command as Command16 } from "commander";
5607
5636
  var pluginListCommand = new Command16("list").description("List plugins installed in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
@@ -5614,7 +5643,7 @@ var pluginListCommand = new Command16("list").description("List plugins installe
5614
5643
  }
5615
5644
  });
5616
5645
  async function runPluginList(options) {
5617
- const servicesDir = join22(options.cwd, "services");
5646
+ const servicesDir = join23(options.cwd, "services");
5618
5647
  if (!existsSync20(servicesDir)) {
5619
5648
  throw new Error(
5620
5649
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
@@ -5661,7 +5690,7 @@ async function runPluginList(options) {
5661
5690
 
5662
5691
  // src/commands/plugin-sync-migrations.ts
5663
5692
  import { existsSync as existsSync21 } from "fs";
5664
- import { join as join23, relative as relative4, resolve as resolve14 } from "path";
5693
+ import { join as join24, relative as relative4, resolve as resolve14 } from "path";
5665
5694
  import chalk17 from "chalk";
5666
5695
  import { Command as Command17 } from "commander";
5667
5696
  var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
@@ -5682,11 +5711,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
5682
5711
  }
5683
5712
  );
5684
5713
  async function runPluginSyncMigrations(name, options, deps) {
5685
- const servicesDir = join23(options.cwd, "services");
5714
+ const servicesDir = join24(options.cwd, "services");
5686
5715
  if (!existsSync21(servicesDir)) {
5687
5716
  throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
5688
5717
  }
5689
- if (name && !existsSync21(join23(servicesDir, name, "biffo.plugin.json"))) {
5718
+ if (name && !existsSync21(join24(servicesDir, name, "biffo.plugin.json"))) {
5690
5719
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
5691
5720
  }
5692
5721
  if (options.dryRun) {
@@ -5723,7 +5752,7 @@ async function runPluginSyncMigrations(name, options, deps) {
5723
5752
 
5724
5753
  // src/commands/plugin-uninstall.ts
5725
5754
  import { existsSync as existsSync22, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
5726
- import { join as join24, resolve as resolve15 } from "path";
5755
+ import { join as join25, resolve as resolve15 } from "path";
5727
5756
  import chalk18 from "chalk";
5728
5757
  import { Command as Command18 } from "commander";
5729
5758
  import inquirer6 from "inquirer";
@@ -5755,15 +5784,15 @@ async function runPluginUninstall(name, options, deps) {
5755
5784
  if (!NAME_PATTERN2.test(name)) {
5756
5785
  throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
5757
5786
  }
5758
- const servicesDir = join24(options.cwd, "services");
5787
+ const servicesDir = join25(options.cwd, "services");
5759
5788
  if (!existsSync22(servicesDir)) {
5760
5789
  throw new Error(
5761
5790
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
5762
5791
  );
5763
5792
  }
5764
- const targetDir = join24(servicesDir, name);
5793
+ const targetDir = join25(servicesDir, name);
5765
5794
  if (!existsSync22(targetDir)) {
5766
- const firstParty = join24(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
5795
+ const firstParty = join25(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
5767
5796
  if (existsSync22(firstParty)) {
5768
5797
  throw new Error(
5769
5798
  `Plugin '${name}' is a first-party plugin at ${pluginDir(name, "first-party")}/, which is template-owned \u2014 \`biffo core upgrade\` would restore it on the next upgrade. Disable it instead by removing '${name}' from \`enabled_plugins\` in infra/environments/<env>/main.tf and re-applying.`
@@ -5772,7 +5801,7 @@ async function runPluginUninstall(name, options, deps) {
5772
5801
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
5773
5802
  }
5774
5803
  const version = readInstalledVersion(targetDir);
5775
- const modulesDir = join24(options.cwd, "modules", "plugins", name);
5804
+ const modulesDir = join25(options.cwd, "modules", "plugins", name);
5776
5805
  const stagePaths = [`services/${name}`];
5777
5806
  if (existsSync22(modulesDir)) {
5778
5807
  stagePaths.push(`modules/plugins/${name}`);
@@ -5833,7 +5862,7 @@ async function runPluginUninstall(name, options, deps) {
5833
5862
  }
5834
5863
  }
5835
5864
  function readInstalledVersion(targetDir) {
5836
- const manifestPath = join24(targetDir, "biffo.plugin.json");
5865
+ const manifestPath = join25(targetDir, "biffo.plugin.json");
5837
5866
  if (!existsSync22(manifestPath)) return void 0;
5838
5867
  try {
5839
5868
  return validateManifest(JSON.parse(readFileSync18(manifestPath, "utf8"))).version;
@@ -5870,7 +5899,7 @@ function printDryRun5(name, version, stagePaths, keepData) {
5870
5899
 
5871
5900
  // src/commands/plugin-upgrade.ts
5872
5901
  import { cpSync as cpSync4, existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync19, rmSync as rmSync8 } from "fs";
5873
- import { join as join25, relative as relative5, resolve as resolve16 } from "path";
5902
+ import { join as join26, relative as relative5, resolve as resolve16 } from "path";
5874
5903
  import chalk19 from "chalk";
5875
5904
  import { Command as Command19 } from "commander";
5876
5905
  import inquirer7 from "inquirer";
@@ -5895,13 +5924,13 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
5895
5924
  });
5896
5925
  async function runPluginUpgrade(target, options, deps) {
5897
5926
  const { name, minor } = parsePluginTarget(target);
5898
- const servicesDir = join25(options.cwd, "services");
5927
+ const servicesDir = join26(options.cwd, "services");
5899
5928
  if (!existsSync23(servicesDir)) {
5900
5929
  throw new Error(
5901
5930
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
5902
5931
  );
5903
5932
  }
5904
- const targetDir = join25(servicesDir, name);
5933
+ const targetDir = join26(servicesDir, name);
5905
5934
  if (!existsSync23(targetDir)) {
5906
5935
  throw new Error(
5907
5936
  `Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
@@ -5916,7 +5945,7 @@ async function runPluginUpgrade(target, options, deps) {
5916
5945
  `Plugin declares required_core_version '${entry.required_core_version}'. The CLI cannot verify this against your deployment \u2014 the Core API exposes no version endpoint and services/api/pyproject.toml's version is a static placeholder, not a real release marker. Confirm compatibility yourself before deploying.`
5917
5946
  );
5918
5947
  }
5919
- const modulesDir = join25(options.cwd, "modules", "plugins", entry.name);
5948
+ const modulesDir = join26(options.cwd, "modules", "plugins", entry.name);
5920
5949
  if (options.dryRun) {
5921
5950
  printDryRun6(entry, currentVersion);
5922
5951
  return;
@@ -5952,7 +5981,7 @@ async function runPluginUpgrade(target, options, deps) {
5952
5981
  if (existsSync23(modulesDir)) {
5953
5982
  rmSync8(modulesDir, { recursive: true, force: true });
5954
5983
  }
5955
- const tfSourceDir = join25(targetDir, "terraform");
5984
+ const tfSourceDir = join26(targetDir, "terraform");
5956
5985
  if (existsSync23(tfSourceDir)) {
5957
5986
  mkdirSync10(modulesDir, { recursive: true });
5958
5987
  cpSync4(tfSourceDir, modulesDir, { recursive: true });
@@ -5987,7 +6016,7 @@ async function runPluginUpgrade(target, options, deps) {
5987
6016
  }
5988
6017
  }
5989
6018
  function readInstalledVersion2(targetDir) {
5990
- const manifestPath = join25(targetDir, "biffo.plugin.json");
6019
+ const manifestPath = join26(targetDir, "biffo.plugin.json");
5991
6020
  if (!existsSync23(manifestPath)) return void 0;
5992
6021
  try {
5993
6022
  return validateManifest(JSON.parse(readFileSync19(manifestPath, "utf8"))).version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.39.0",
3
+ "version": "0.41.1",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,12 +23,13 @@
23
23
  "files": [
24
24
  "dist",
25
25
  "schemas",
26
- "core.version"
26
+ "core.version",
27
+ "_skeletons"
27
28
  ],
28
29
  "scripts": {
29
30
  "build": "tsup src/index.ts --format esm --dts --clean",
30
- "prepack": "node scripts/sync-core-version.mjs",
31
- "postpack": "node -e \"require('node:fs').rmSync('core.version',{force:true})\"",
31
+ "prepack": "node scripts/sync-packaged-assets.mjs",
32
+ "postpack": "node scripts/sync-packaged-assets.mjs --clean",
32
33
  "dev": "tsx watch src/index.ts",
33
34
  "lint": "eslint src/",
34
35
  "lint:fix": "eslint src/ --fix",