@decocms/blocks-cli 7.18.0 → 7.20.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks-cli",
3
- "version": "7.18.0",
3
+ "version": "7.20.0",
4
4
  "type": "module",
5
5
  "description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "lint:unused": "knip"
31
31
  },
32
32
  "dependencies": {
33
- "@decocms/blocks": "7.18.0",
33
+ "@decocms/blocks": "7.20.0",
34
34
  "ts-morph": "^27.0.0",
35
35
  "tsx": "^4.22.5"
36
36
  },
@@ -15,7 +15,7 @@ import * as fs from "node:fs";
15
15
  import * as os from "node:os";
16
16
  import * as path from "node:path";
17
17
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
18
- import { parseCliOptions } from "./generate";
18
+ import { buildPlan, parseCliOptions } from "./generate";
19
19
 
20
20
  const SCRIPT = path.resolve(__dirname, "generate.ts");
21
21
 
@@ -213,6 +213,43 @@ describe("parseCliOptions", () => {
213
213
  expect(parseCliOptions(["--no-registry"]).registry).toBe(false);
214
214
  expect(parseCliOptions([]).registry).toBe(null);
215
215
  });
216
+
217
+ it("accepts --root and a bare positional path as the effective root", () => {
218
+ expect(parseCliOptions(["--root", "apps/foo"]).root).toBe("apps/foo");
219
+ expect(parseCliOptions(["apps/foo"]).root).toBe("apps/foo");
220
+ expect(parseCliOptions([]).root).toBe(null);
221
+ });
222
+
223
+ it("rejects a second positional but still rejects unknown flags", () => {
224
+ expect(() => parseCliOptions(["a", "b"])).toThrow(/Unknown option/);
225
+ expect(() => parseCliOptions(["--wat"])).toThrow(/Unknown option/);
226
+ });
227
+ });
228
+
229
+ describe("buildPlan --platform eitri", () => {
230
+ it("runs only schema + blocks and makes schema self-contained", () => {
231
+ // makeFixture installs @decocms/tanstack + src/sections + src/loaders, which
232
+ // would normally enable sections/loaders/invoke; --platform eitri overrides.
233
+ const dir = makeFixture({ withInvoke: true });
234
+ try {
235
+ const plans = buildPlan(dir, parseCliOptions(["--platform", "eitri"]));
236
+ const byName = Object.fromEntries(plans.map((p) => [p.name, p]));
237
+
238
+ expect(byName.schema.enabled).toBe(true);
239
+ expect(byName.blocks.enabled).toBe(true);
240
+ for (const name of ["manifest", "sections", "loaders", "invoke"]) {
241
+ expect(byName[name].enabled).toBe(false);
242
+ expect(byName[name].disabledReason).toBe("not used by --platform eitri");
243
+ }
244
+
245
+ // schema is asked to bake composeMeta's framework block types in.
246
+ expect(byName.schema.args).toContain("--compose");
247
+ const fwIdx = byName.schema.args.indexOf("--framework");
248
+ expect(byName.schema.args[fwIdx + 1]).toBe("eitri");
249
+ } finally {
250
+ fs.rmSync(dir, { recursive: true, force: true });
251
+ }
252
+ });
216
253
  });
217
254
 
218
255
  // ---------------------------------------------------------------------------
@@ -171,6 +171,15 @@ Selection:
171
171
  --skip <names> Comma-separated generators to exclude
172
172
  --force Ignore the cache; run everything selected
173
173
  --dry-run Print what would run / skip / stay disabled, then exit
174
+ --root <dir> Effective working directory (also accepted as a bare
175
+ positional path). Input dirs + .deco/ output resolve
176
+ against it, so a monorepo can target a sub-app folder
177
+ without cd. (default: process.cwd())
178
+
179
+ Platform:
180
+ --platform <name> Forwarded to schema. With "eitri": runs ONLY schema +
181
+ blocks (skips manifest/sections/loaders/invoke) and makes
182
+ the schema self-contained (composeMeta at gen time).
174
183
 
175
184
  Forwarded to the individual generators:
176
185
  --blocks-dir <dir> blocks + manifest input (default .deco/blocks)
@@ -220,6 +229,13 @@ export interface CliOptions {
220
229
  namespace: string | null;
221
230
  platform: string | null;
222
231
  skipApps: boolean;
232
+ /**
233
+ * Effective working directory. `.deco/` output and input dirs resolve
234
+ * against this instead of process.cwd(), so a monorepo can target a
235
+ * sub-app folder without `cd`. Set via `--root <dir>` or a bare positional
236
+ * path. Relative to process.cwd().
237
+ */
238
+ root: string | null;
223
239
  }
224
240
 
225
241
  function parseNames(raw: string, flag: string): GeneratorName[] {
@@ -257,6 +273,7 @@ export function parseCliOptions(argv: string[]): CliOptions {
257
273
  namespace: null,
258
274
  platform: null,
259
275
  skipApps: false,
276
+ root: null,
260
277
  };
261
278
 
262
279
  const valueOf = (i: number, flag: string): string => {
@@ -335,7 +352,18 @@ export function parseCliOptions(argv: string[]): CliOptions {
335
352
  case "--skip-apps":
336
353
  opts.skipApps = true;
337
354
  break;
355
+ case "--root":
356
+ opts.root = valueOf(i, a);
357
+ i++;
358
+ break;
338
359
  default:
360
+ // A single bare (non-flag) argument is accepted as the root path, so
361
+ // `generate ./apps/foo` works like `generate --root ./apps/foo`.
362
+ // Anything else (or a second positional) is still a hard error.
363
+ if (!a.startsWith("--") && opts.root === null) {
364
+ opts.root = a;
365
+ break;
366
+ }
339
367
  throw new Error(`Unknown option "${a}". Run with --help for usage.`);
340
368
  }
341
369
  }
@@ -625,6 +653,9 @@ export function buildPlan(cwd: string, opts: CliOptions): GeneratorPlan[] {
625
653
  ...(opts.namespace ? ["--namespace", opts.namespace] : []),
626
654
  ...(opts.platform ? ["--platform", opts.platform] : []),
627
655
  ...(opts.skipApps ? ["--skip-apps"] : []),
656
+ // Eitri (and any FS-only consumer) needs a self-contained meta.gen.json:
657
+ // bake composeMeta's framework block types in at generation time.
658
+ ...(opts.platform === "eitri" ? ["--compose", "--framework", "eitri"] : []),
628
659
  ],
629
660
  stage: 2,
630
661
  ...enabledIf(
@@ -661,6 +692,26 @@ export function buildPlan(cwd: string, opts: CliOptions): GeneratorPlan[] {
661
692
  },
662
693
  ];
663
694
 
695
+ // --platform eitri: deco only AUTHORS config for Eitri (Eitri renders on its
696
+ // own mobile runtime), so the only useful artifacts are the self-contained
697
+ // meta.gen.json (schema) and the bundled decofile snapshot (blocks). The
698
+ // React-runtime generators (manifest/sections/loaders/invoke) would be dead
699
+ // weight, and `sections` in particular would otherwise auto-enable because
700
+ // src/sections exists. Applied before --only/--skip so those still win.
701
+ if (opts.platform === "eitri") {
702
+ for (const plan of plans) {
703
+ if (plan.name === "blocks") {
704
+ // Emit the snapshot even when .deco/blocks is empty (a fresh Eitri app
705
+ // has no authored content yet) — generate-blocks writes an empty barrel.
706
+ plan.enabled = true;
707
+ plan.disabledReason = undefined;
708
+ } else if (plan.name !== "schema") {
709
+ plan.enabled = false;
710
+ plan.disabledReason = "not used by --platform eitri";
711
+ }
712
+ }
713
+ }
714
+
664
715
  // Apply --only / --skip on top of auto-enablement. --only also FORCES a
665
716
  // generator on (explicit intent beats detection) unless its hard inputs
666
717
  // are missing in a way that would just crash the child.
@@ -965,6 +1016,19 @@ export async function runGenerate(argv: string[], cwd = process.cwd()): Promise<
965
1016
  return 0;
966
1017
  }
967
1018
 
1019
+ // --root / positional path relocates the effective working directory: every
1020
+ // input dir and the `.deco/` output tree resolve against it (child
1021
+ // generators are spawned with this cwd), so a monorepo can target a sub-app
1022
+ // folder without an explicit `cd`.
1023
+ if (opts.root) {
1024
+ cwd = path.resolve(cwd, opts.root);
1025
+ if (!fs.existsSync(cwd)) {
1026
+ console.error(`[generate] --root path does not exist: ${cwd}`);
1027
+ return 1;
1028
+ }
1029
+ console.log(`[generate] root: ${cwd}`);
1030
+ }
1031
+
968
1032
  const totalStarted = Date.now();
969
1033
  const plans = buildPlan(cwd, opts);
970
1034
  const versions = decoPackageVersions(cwd);