@biffo/cli 0.38.0 → 0.41.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.
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 +113 -87
  73. package/package.json +5 -4
package/core.version CHANGED
@@ -1 +1 @@
1
- 0.38.0
1
+ 0.41.0
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}`);
@@ -4001,7 +4023,7 @@ function readExistingSiblingOrigins(filePath) {
4001
4023
  }
4002
4024
  }
4003
4025
  function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
4004
- const cdnVarsPath = join15(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
4026
+ const cdnVarsPath = join16(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
4005
4027
  let declaresSiblingOrigins = false;
4006
4028
  try {
4007
4029
  declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync11(cdnVarsPath, "utf8"));
@@ -4014,7 +4036,7 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
4014
4036
  );
4015
4037
  }
4016
4038
  if (!isRootPathPrefix(pathPrefix)) return;
4017
- const cdnMainPath = join15(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
4039
+ const cdnMainPath = join16(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
4018
4040
  let supportsRoot = false;
4019
4041
  try {
4020
4042
  supportsRoot = /root_sibling_registered/.test(readFileSync11(cdnMainPath, "utf8"));
@@ -4047,8 +4069,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
4047
4069
  for (const env of config.environments) {
4048
4070
  const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
4049
4071
  const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
4050
- const relativePath = join15("infra", "environments", env, "siblings.auto.tfvars.json");
4051
- const filePath = join15(cloneDir, relativePath);
4072
+ const relativePath = join16("infra", "environments", env, "siblings.auto.tfvars.json");
4073
+ const filePath = join16(cloneDir, relativePath);
4052
4074
  const existing = readExistingSiblingOrigins(filePath);
4053
4075
  const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
4054
4076
  name,
@@ -4125,15 +4147,18 @@ function awsConfig(config) {
4125
4147
  return config.cloud.config;
4126
4148
  }
4127
4149
  function defaultSiblingTemplateRoot() {
4128
- let dir = dirname6(new URL(import.meta.url).pathname);
4150
+ const start = dirname6(fileURLToPath4(import.meta.url));
4151
+ let dir = start;
4129
4152
  for (; ; ) {
4130
- const candidate = join15(dir, "_skeletons", "sibling-template");
4153
+ const candidate = join16(dir, "_skeletons", "sibling-template");
4131
4154
  if (existsSync14(candidate)) return candidate;
4132
4155
  const parent = dirname6(dir);
4133
4156
  if (parent === dir) break;
4134
4157
  dir = parent;
4135
4158
  }
4136
- return resolve9(process.cwd(), "_skeletons", "sibling-template");
4159
+ throw new Error(
4160
+ `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.`
4161
+ );
4137
4162
  }
4138
4163
 
4139
4164
  // src/commands/init.ts
@@ -4557,14 +4582,14 @@ import { Command as Command20 } from "commander";
4557
4582
 
4558
4583
  // src/commands/plugin-create.ts
4559
4584
  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";
4585
+ import { dirname as dirname8, join as join19, resolve as resolve11 } from "path";
4586
+ import { fileURLToPath as fileURLToPath5 } from "url";
4562
4587
  import chalk13 from "chalk";
4563
4588
  import { Command as Command13 } from "commander";
4564
4589
 
4565
4590
  // src/lib/plugin-locations.ts
4566
- import { existsSync as existsSync15, readdirSync as readdirSync8 } from "fs";
4567
- import { join as join16 } from "path";
4591
+ import { existsSync as existsSync15, readdirSync as readdirSync9 } from "fs";
4592
+ import { join as join17 } from "path";
4568
4593
  var FIRST_PARTY_PLUGINS_DIR = "_plugins";
4569
4594
  var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
4570
4595
  function pluginDir(name, channel) {
@@ -4573,10 +4598,10 @@ function pluginDir(name, channel) {
4573
4598
  function scanDir(absDir, relDir, channel) {
4574
4599
  if (!existsSync15(absDir)) return [];
4575
4600
  const found = [];
4576
- for (const entry of readdirSync8(absDir, { withFileTypes: true })) {
4601
+ for (const entry of readdirSync9(absDir, { withFileTypes: true })) {
4577
4602
  if (!entry.isDirectory()) continue;
4578
4603
  if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
4579
- const manifestPath = join16(absDir, entry.name, PLUGIN_MANIFEST_FILE);
4604
+ const manifestPath = join17(absDir, entry.name, PLUGIN_MANIFEST_FILE);
4580
4605
  if (!existsSync15(manifestPath)) continue;
4581
4606
  found.push({
4582
4607
  dirName: entry.name,
@@ -4588,11 +4613,11 @@ function scanDir(absDir, relDir, channel) {
4588
4613
  return found;
4589
4614
  }
4590
4615
  function findInstalledPlugins(cwd) {
4591
- const servicesDir = join16(cwd, "services");
4616
+ const servicesDir = join17(cwd, "services");
4592
4617
  return [
4593
4618
  ...scanDir(servicesDir, "services", "third-party"),
4594
4619
  ...scanDir(
4595
- join16(servicesDir, FIRST_PARTY_PLUGINS_DIR),
4620
+ join17(servicesDir, FIRST_PARTY_PLUGINS_DIR),
4596
4621
  `services/${FIRST_PARTY_PLUGINS_DIR}`,
4597
4622
  "first-party"
4598
4623
  )
@@ -4755,10 +4780,10 @@ import {
4755
4780
  existsSync as existsSync16,
4756
4781
  mkdirSync as mkdirSync7,
4757
4782
  readFileSync as readFileSync13,
4758
- readdirSync as readdirSync9,
4783
+ readdirSync as readdirSync10,
4759
4784
  writeFileSync as writeFileSync7
4760
4785
  } from "fs";
4761
- import { dirname as dirname7, join as join17 } from "path";
4786
+ import { dirname as dirname7, join as join18 } from "path";
4762
4787
  var STANDALONE_ONLY_ENTRIES = {
4763
4788
  ".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
4764
4789
  "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 +4839,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4814
4839
  if (!existsSync16(skeletonRoot)) {
4815
4840
  throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
4816
4841
  }
4817
- if (!existsSync16(join17(skeletonRoot, "terraform"))) {
4842
+ if (!existsSync16(join18(skeletonRoot, "terraform"))) {
4818
4843
  throw new Error(
4819
4844
  `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
4845
  );
@@ -4822,8 +4847,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4822
4847
  const skipped = [];
4823
4848
  const files = [];
4824
4849
  const walk = (relDir) => {
4825
- const absDir = join17(skeletonRoot, relDir);
4826
- for (const entry of readdirSync9(absDir, { withFileTypes: true }).sort(
4850
+ const absDir = join18(skeletonRoot, relDir);
4851
+ for (const entry of readdirSync10(absDir, { withFileTypes: true }).sort(
4827
4852
  (a, b) => a.name.localeCompare(b.name)
4828
4853
  )) {
4829
4854
  if (NEVER_COPY.has(entry.name)) continue;
@@ -4837,14 +4862,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4837
4862
  continue;
4838
4863
  }
4839
4864
  const destRel = applySubstitutions(relPath, names);
4840
- const destPath = join17(destDir, destRel);
4865
+ const destPath = join18(destDir, destRel);
4841
4866
  mkdirSync7(dirname7(destPath), { recursive: true });
4842
4867
  if (BINARY_EXTENSIONS.test(entry.name)) {
4843
- copyFileSync(join17(skeletonRoot, relPath), destPath);
4868
+ copyFileSync(join18(skeletonRoot, relPath), destPath);
4844
4869
  } else {
4845
4870
  writeFileSync7(
4846
4871
  destPath,
4847
- applySubstitutions(readFileSync13(join17(skeletonRoot, relPath), "utf8"), names)
4872
+ applySubstitutions(readFileSync13(join18(skeletonRoot, relPath), "utf8"), names)
4848
4873
  );
4849
4874
  }
4850
4875
  files.push(destRel);
@@ -4861,7 +4886,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
4861
4886
  function findSkeletonRoot(startDir, skeleton) {
4862
4887
  let dir = startDir;
4863
4888
  for (; ; ) {
4864
- const candidate = join17(dir, "_skeletons", skeleton);
4889
+ const candidate = join18(dir, "_skeletons", skeleton);
4865
4890
  if (existsSync16(candidate)) return candidate;
4866
4891
  const parent = dirname7(dir);
4867
4892
  if (parent === dir) return null;
@@ -4899,7 +4924,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
4899
4924
  );
4900
4925
  async function runPluginCreate(name, options, deps) {
4901
4926
  const names = deriveNames(name);
4902
- const isInstance = existsSync17(join18(options.cwd, INSTANCE_CORE_FILE));
4927
+ const isInstance = existsSync17(join19(options.cwd, INSTANCE_CORE_FILE));
4903
4928
  if (options.firstParty && isInstance) {
4904
4929
  throw new Error(
4905
4930
  `--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 +4932,8 @@ async function runPluginCreate(name, options, deps) {
4907
4932
  }
4908
4933
  const channel = options.firstParty ? "first-party" : "third-party";
4909
4934
  const relDir = pluginDir(names.slug, channel);
4910
- const destDir = join18(options.cwd, relDir);
4911
- const servicesDir = join18(options.cwd, "services");
4935
+ const destDir = join19(options.cwd, relDir);
4936
+ const servicesDir = join19(options.cwd, "services");
4912
4937
  if (!existsSync17(servicesDir)) {
4913
4938
  throw new Error(
4914
4939
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
@@ -4917,8 +4942,8 @@ async function runPluginCreate(name, options, deps) {
4917
4942
  if (existsSync17(destDir)) {
4918
4943
  throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
4919
4944
  }
4920
- const here = dirname8(fileURLToPath4(import.meta.url));
4921
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join18(options.cwd, "_skeletons", "plugin-template");
4945
+ const here = dirname8(fileURLToPath5(import.meta.url));
4946
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join19(options.cwd, "_skeletons", "plugin-template");
4922
4947
  if (!existsSync17(skeletonRoot)) {
4923
4948
  throw new Error(
4924
4949
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
@@ -4929,11 +4954,12 @@ async function runPluginCreate(name, options, deps) {
4929
4954
  return;
4930
4955
  }
4931
4956
  const { files, skipped } = scaffoldPlugin(skeletonRoot, destDir, names);
4957
+ restorePackagedDotfiles(destDir);
4932
4958
  log.success(`Scaffolded ${files.length} file(s) into ${relDir}/`);
4933
4959
  for (const { entry, reason } of skipped) {
4934
4960
  log.info(`Skipped ${entry} \u2014 ${reason}`);
4935
4961
  }
4936
- const manifestPath = join18(destDir, "biffo.plugin.json");
4962
+ const manifestPath = join19(destDir, "biffo.plugin.json");
4937
4963
  const manifest = validateManifest(JSON.parse(readFileSync14(manifestPath, "utf8")));
4938
4964
  if (manifest.name !== names.slug) {
4939
4965
  throw new Error(
@@ -5125,13 +5151,13 @@ function printEntry(entry) {
5125
5151
 
5126
5152
  // src/commands/plugin-install.ts
5127
5153
  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";
5154
+ import { basename, join as join22, relative as relative3, resolve as resolve12 } from "path";
5129
5155
  import chalk15 from "chalk";
5130
5156
  import { Command as Command15 } from "commander";
5131
5157
 
5132
5158
  // src/adapters/plugin-migrations/index.ts
5133
5159
  import { execa as execa3 } from "execa";
5134
- import { join as join19 } from "path";
5160
+ import { join as join20 } from "path";
5135
5161
  var PluginMigrationsAdapter = class {
5136
5162
  /**
5137
5163
  * Generates migration file(s) for `pluginNames` (every discovered
@@ -5140,22 +5166,22 @@ var PluginMigrationsAdapter = class {
5140
5166
  * or declared no tables.
5141
5167
  */
5142
5168
  async generate(cwd, pluginNames) {
5143
- const scriptPath = join19(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
5169
+ const scriptPath = join20(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
5144
5170
  const args = [
5145
5171
  "run",
5146
5172
  "python",
5147
5173
  scriptPath,
5148
5174
  "--services-root",
5149
- join19(cwd, "services"),
5175
+ join20(cwd, "services"),
5150
5176
  "--versions-dir",
5151
- join19(cwd, "services", "api", "migrations", "versions")
5177
+ join20(cwd, "services", "api", "migrations", "versions")
5152
5178
  ];
5153
5179
  for (const name of pluginNames ?? []) {
5154
5180
  args.push("--plugin", name);
5155
5181
  }
5156
5182
  let result;
5157
5183
  try {
5158
- result = await execa3("uv", args, { cwd: join19(cwd, "services", "api") });
5184
+ result = await execa3("uv", args, { cwd: join20(cwd, "services", "api") });
5159
5185
  } catch (err) {
5160
5186
  const cause = err;
5161
5187
  if (cause.code === "ENOENT") {
@@ -5172,8 +5198,8 @@ var PluginMigrationsAdapter = class {
5172
5198
  };
5173
5199
 
5174
5200
  // 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";
5201
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
5202
+ import { join as join21 } from "path";
5177
5203
  var TEMPLATE_MODULE_DIR = "_template";
5178
5204
  var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
5179
5205
  var GENERATED_TF_FILE = "plugins.generated.tf";
@@ -5191,45 +5217,45 @@ function standardArguments(pluginName, handler) {
5191
5217
  ];
5192
5218
  }
5193
5219
  function listPluginModules(cwd) {
5194
- const dir = join20(cwd, "modules", "plugins");
5220
+ const dir = join21(cwd, "modules", "plugins");
5195
5221
  let entries;
5196
5222
  try {
5197
- entries = readdirSync10(dir, { withFileTypes: true });
5223
+ entries = readdirSync11(dir, { withFileTypes: true });
5198
5224
  } catch {
5199
5225
  return [];
5200
5226
  }
5201
5227
  return entries.filter((e) => e.isDirectory() && e.name !== TEMPLATE_MODULE_DIR && !e.name.startsWith(".")).map((e) => e.name).sort();
5202
5228
  }
5203
5229
  function listEnvironments(cwd) {
5204
- const dir = join20(cwd, "infra", "environments");
5230
+ const dir = join21(cwd, "infra", "environments");
5205
5231
  let entries;
5206
5232
  try {
5207
- entries = readdirSync10(dir, { withFileTypes: true });
5233
+ entries = readdirSync11(dir, { withFileTypes: true });
5208
5234
  } catch {
5209
5235
  return [];
5210
5236
  }
5211
5237
  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");
5238
+ if (!e.isDirectory() || !existsSync18(join21(dir, e.name, "main.tf"))) return false;
5239
+ return declaredVariables(join21(dir, e.name)).has("enabled_plugins");
5214
5240
  }).map((e) => e.name).sort();
5215
5241
  }
5216
5242
  function listUnwirableEnvironments(cwd) {
5217
- const dir = join20(cwd, "infra", "environments");
5243
+ const dir = join21(cwd, "infra", "environments");
5218
5244
  let entries;
5219
5245
  try {
5220
- entries = readdirSync10(dir, { withFileTypes: true });
5246
+ entries = readdirSync11(dir, { withFileTypes: true });
5221
5247
  } catch {
5222
5248
  return [];
5223
5249
  }
5224
5250
  return entries.filter(
5225
- (e) => e.isDirectory() && existsSync18(join20(dir, e.name, "main.tf")) && !declaredVariables(join20(dir, e.name)).has("enabled_plugins")
5251
+ (e) => e.isDirectory() && existsSync18(join21(dir, e.name, "main.tf")) && !declaredVariables(join21(dir, e.name)).has("enabled_plugins")
5226
5252
  ).map((e) => e.name).sort();
5227
5253
  }
5228
5254
  function declaredVariables(moduleDir) {
5229
5255
  const names = /* @__PURE__ */ new Set();
5230
5256
  let entries;
5231
5257
  try {
5232
- entries = readdirSync10(moduleDir, { withFileTypes: true });
5258
+ entries = readdirSync11(moduleDir, { withFileTypes: true });
5233
5259
  } catch {
5234
5260
  return names;
5235
5261
  }
@@ -5237,7 +5263,7 @@ function declaredVariables(moduleDir) {
5237
5263
  if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
5238
5264
  let contents;
5239
5265
  try {
5240
- contents = readFileSync15(join20(moduleDir, entry.name), "utf8");
5266
+ contents = readFileSync15(join21(moduleDir, entry.name), "utf8");
5241
5267
  } catch {
5242
5268
  continue;
5243
5269
  }
@@ -5309,12 +5335,12 @@ function syncPluginTerraform(cwd) {
5309
5335
  const changedPaths = [];
5310
5336
  const rendered = plugins.map((name) => ({
5311
5337
  name,
5312
- declaredVariables: declaredVariables(join20(cwd, "modules", "plugins", name))
5338
+ declaredVariables: declaredVariables(join21(cwd, "modules", "plugins", name))
5313
5339
  }));
5314
5340
  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);
5341
+ const envDir = join21(cwd, "infra", "environments", env);
5342
+ const tfPath = join21(envDir, GENERATED_TF_FILE);
5343
+ const tfvarsPath = join21(envDir, GENERATED_TFVARS_FILE);
5318
5344
  const relBase = `infra/environments/${env}`;
5319
5345
  if (plugins.length === 0) {
5320
5346
  for (const [abs, rel] of [
@@ -5384,7 +5410,7 @@ function resolveLocalPlugin(localPath) {
5384
5410
  if (!statSync5(localPath).isDirectory()) {
5385
5411
  throw new Error(`--local path is not a directory: ${localPath}`);
5386
5412
  }
5387
- const manifestPath = join21(localPath, "biffo.plugin.json");
5413
+ const manifestPath = join22(localPath, "biffo.plugin.json");
5388
5414
  if (!existsSync19(manifestPath)) {
5389
5415
  throw new Error(
5390
5416
  `${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 +5437,7 @@ function parsePluginTarget(target) {
5411
5437
  async function cloneAndValidatePlugin(entry, git) {
5412
5438
  const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
5413
5439
  try {
5414
- const manifestPath = join21(tmpDir, "biffo.plugin.json");
5440
+ const manifestPath = join22(tmpDir, "biffo.plugin.json");
5415
5441
  if (!existsSync19(manifestPath)) {
5416
5442
  throw new Error(
5417
5443
  `Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
@@ -5440,7 +5466,7 @@ async function runPluginInstall(target, options, deps) {
5440
5466
  `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
5467
  );
5442
5468
  }
5443
- const servicesDir = join21(options.cwd, "services");
5469
+ const servicesDir = join22(options.cwd, "services");
5444
5470
  if (!existsSync19(servicesDir)) {
5445
5471
  throw new Error(
5446
5472
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
@@ -5459,8 +5485,8 @@ async function runPluginInstall(target, options, deps) {
5459
5485
  }
5460
5486
  const pluginName = entry ? entry.name : source.name;
5461
5487
  const relTargetDir = pluginDir(pluginName, "third-party");
5462
- const targetDir = join21(options.cwd, relTargetDir);
5463
- const modulesDir = join21(options.cwd, "modules", "plugins", pluginName);
5488
+ const targetDir = join22(options.cwd, relTargetDir);
5489
+ const modulesDir = join22(options.cwd, "modules", "plugins", pluginName);
5464
5490
  const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
5465
5491
  if (existsSync19(targetDir) && !inTreeSource) {
5466
5492
  throw new Error(
@@ -5505,7 +5531,7 @@ async function runPluginInstall(target, options, deps) {
5505
5531
  log.success(`Installed plugin source at ${relTargetDir}/`);
5506
5532
  }
5507
5533
  const stagePaths = [relTargetDir];
5508
- const tfSourceDir = join21(targetDir, "terraform");
5534
+ const tfSourceDir = join22(targetDir, "terraform");
5509
5535
  if (existsSync19(tfSourceDir)) {
5510
5536
  mkdirSync9(modulesDir, { recursive: true });
5511
5537
  cpSync3(tfSourceDir, modulesDir, { recursive: true });
@@ -5601,7 +5627,7 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
5601
5627
 
5602
5628
  // src/commands/plugin-list.ts
5603
5629
  import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
5604
- import { join as join22, resolve as resolve13 } from "path";
5630
+ import { join as join23, resolve as resolve13 } from "path";
5605
5631
  import chalk16 from "chalk";
5606
5632
  import { Command as Command16 } from "commander";
5607
5633
  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 +5640,7 @@ var pluginListCommand = new Command16("list").description("List plugins installe
5614
5640
  }
5615
5641
  });
5616
5642
  async function runPluginList(options) {
5617
- const servicesDir = join22(options.cwd, "services");
5643
+ const servicesDir = join23(options.cwd, "services");
5618
5644
  if (!existsSync20(servicesDir)) {
5619
5645
  throw new Error(
5620
5646
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
@@ -5661,7 +5687,7 @@ async function runPluginList(options) {
5661
5687
 
5662
5688
  // src/commands/plugin-sync-migrations.ts
5663
5689
  import { existsSync as existsSync21 } from "fs";
5664
- import { join as join23, relative as relative4, resolve as resolve14 } from "path";
5690
+ import { join as join24, relative as relative4, resolve as resolve14 } from "path";
5665
5691
  import chalk17 from "chalk";
5666
5692
  import { Command as Command17 } from "commander";
5667
5693
  var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
@@ -5682,11 +5708,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
5682
5708
  }
5683
5709
  );
5684
5710
  async function runPluginSyncMigrations(name, options, deps) {
5685
- const servicesDir = join23(options.cwd, "services");
5711
+ const servicesDir = join24(options.cwd, "services");
5686
5712
  if (!existsSync21(servicesDir)) {
5687
5713
  throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
5688
5714
  }
5689
- if (name && !existsSync21(join23(servicesDir, name, "biffo.plugin.json"))) {
5715
+ if (name && !existsSync21(join24(servicesDir, name, "biffo.plugin.json"))) {
5690
5716
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
5691
5717
  }
5692
5718
  if (options.dryRun) {
@@ -5723,7 +5749,7 @@ async function runPluginSyncMigrations(name, options, deps) {
5723
5749
 
5724
5750
  // src/commands/plugin-uninstall.ts
5725
5751
  import { existsSync as existsSync22, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
5726
- import { join as join24, resolve as resolve15 } from "path";
5752
+ import { join as join25, resolve as resolve15 } from "path";
5727
5753
  import chalk18 from "chalk";
5728
5754
  import { Command as Command18 } from "commander";
5729
5755
  import inquirer6 from "inquirer";
@@ -5755,15 +5781,15 @@ async function runPluginUninstall(name, options, deps) {
5755
5781
  if (!NAME_PATTERN2.test(name)) {
5756
5782
  throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
5757
5783
  }
5758
- const servicesDir = join24(options.cwd, "services");
5784
+ const servicesDir = join25(options.cwd, "services");
5759
5785
  if (!existsSync22(servicesDir)) {
5760
5786
  throw new Error(
5761
5787
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
5762
5788
  );
5763
5789
  }
5764
- const targetDir = join24(servicesDir, name);
5790
+ const targetDir = join25(servicesDir, name);
5765
5791
  if (!existsSync22(targetDir)) {
5766
- const firstParty = join24(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
5792
+ const firstParty = join25(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
5767
5793
  if (existsSync22(firstParty)) {
5768
5794
  throw new Error(
5769
5795
  `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 +5798,7 @@ async function runPluginUninstall(name, options, deps) {
5772
5798
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
5773
5799
  }
5774
5800
  const version = readInstalledVersion(targetDir);
5775
- const modulesDir = join24(options.cwd, "modules", "plugins", name);
5801
+ const modulesDir = join25(options.cwd, "modules", "plugins", name);
5776
5802
  const stagePaths = [`services/${name}`];
5777
5803
  if (existsSync22(modulesDir)) {
5778
5804
  stagePaths.push(`modules/plugins/${name}`);
@@ -5833,7 +5859,7 @@ async function runPluginUninstall(name, options, deps) {
5833
5859
  }
5834
5860
  }
5835
5861
  function readInstalledVersion(targetDir) {
5836
- const manifestPath = join24(targetDir, "biffo.plugin.json");
5862
+ const manifestPath = join25(targetDir, "biffo.plugin.json");
5837
5863
  if (!existsSync22(manifestPath)) return void 0;
5838
5864
  try {
5839
5865
  return validateManifest(JSON.parse(readFileSync18(manifestPath, "utf8"))).version;
@@ -5870,7 +5896,7 @@ function printDryRun5(name, version, stagePaths, keepData) {
5870
5896
 
5871
5897
  // src/commands/plugin-upgrade.ts
5872
5898
  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";
5899
+ import { join as join26, relative as relative5, resolve as resolve16 } from "path";
5874
5900
  import chalk19 from "chalk";
5875
5901
  import { Command as Command19 } from "commander";
5876
5902
  import inquirer7 from "inquirer";
@@ -5895,13 +5921,13 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
5895
5921
  });
5896
5922
  async function runPluginUpgrade(target, options, deps) {
5897
5923
  const { name, minor } = parsePluginTarget(target);
5898
- const servicesDir = join25(options.cwd, "services");
5924
+ const servicesDir = join26(options.cwd, "services");
5899
5925
  if (!existsSync23(servicesDir)) {
5900
5926
  throw new Error(
5901
5927
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
5902
5928
  );
5903
5929
  }
5904
- const targetDir = join25(servicesDir, name);
5930
+ const targetDir = join26(servicesDir, name);
5905
5931
  if (!existsSync23(targetDir)) {
5906
5932
  throw new Error(
5907
5933
  `Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
@@ -5916,7 +5942,7 @@ async function runPluginUpgrade(target, options, deps) {
5916
5942
  `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
5943
  );
5918
5944
  }
5919
- const modulesDir = join25(options.cwd, "modules", "plugins", entry.name);
5945
+ const modulesDir = join26(options.cwd, "modules", "plugins", entry.name);
5920
5946
  if (options.dryRun) {
5921
5947
  printDryRun6(entry, currentVersion);
5922
5948
  return;
@@ -5952,7 +5978,7 @@ async function runPluginUpgrade(target, options, deps) {
5952
5978
  if (existsSync23(modulesDir)) {
5953
5979
  rmSync8(modulesDir, { recursive: true, force: true });
5954
5980
  }
5955
- const tfSourceDir = join25(targetDir, "terraform");
5981
+ const tfSourceDir = join26(targetDir, "terraform");
5956
5982
  if (existsSync23(tfSourceDir)) {
5957
5983
  mkdirSync10(modulesDir, { recursive: true });
5958
5984
  cpSync4(tfSourceDir, modulesDir, { recursive: true });
@@ -5987,7 +6013,7 @@ async function runPluginUpgrade(target, options, deps) {
5987
6013
  }
5988
6014
  }
5989
6015
  function readInstalledVersion2(targetDir) {
5990
- const manifestPath = join25(targetDir, "biffo.plugin.json");
6016
+ const manifestPath = join26(targetDir, "biffo.plugin.json");
5991
6017
  if (!existsSync23(manifestPath)) return void 0;
5992
6018
  try {
5993
6019
  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.38.0",
3
+ "version": "0.41.0",
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",