Skip to content

Commit 0cccc38

Browse files
committed
Add --skip-install option to fedify init
Assisted-by: Claude Code:claude-opus-4-7
1 parent 8db5848 commit 0cccc38

12 files changed

Lines changed: 105 additions & 5 deletions

File tree

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,17 @@ To be released.
265265
- Added `SqliteMessageQueue.getDepth()` for reporting queued, ready, and
266266
delayed message counts. [[#735], [#748]]
267267

268+
### @fedify/init
269+
270+
- Added a `--skip-install` option to `fedify init` that skips automatic
271+
dependency installation after scaffolding. This is useful for CI
272+
environments, monorepo workspaces that install dependencies from the
273+
root, or when you want to inspect the generated files before
274+
installing. [[#720], [#776] by fru1tworld]
275+
276+
[#720]: https://github.com/fedify-dev/fedify/issues/720
277+
[#776]: https://github.com/fedify-dev/fedify/pull/776
278+
268279
### Claude Code plugin
269280

270281
- Added a Claude Code plugin at *claude-plugin/*, installable with:

docs/cli.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,28 @@ the selected framework scaffolder accepts them. Some scaffolders, such as
372372
own confirmation prompt, while a freshly initialized *.git* directory remains
373373
acceptable.
374374

375+
### `--skip-install`: Skip installing dependencies
376+
377+
*This option is available since Fedify 2.3.0.*
378+
379+
By default, `fedify init` runs the selected package manager's install command
380+
right after scaffolding the project. The `--skip-install` option scaffolds the
381+
files without running install, which is useful when:
382+
383+
- installation is handled separately in a CI pipeline;
384+
- the new project lives inside a monorepo whose dependencies are installed
385+
from the workspace root; or
386+
- you want to inspect the generated files before installing.
387+
388+
~~~~ sh
389+
fedify init my-fedify-project --skip-install
390+
~~~~
391+
392+
After scaffolding, `fedify init` prints the command to run to install
393+
dependencies later. Other steps such as creating files, applying patches, and
394+
running the framework-specific scaffolder (e.g., *create-next-app*) still
395+
happen as usual; only the final install step is skipped.
396+
375397

376398
`fedify lookup`: Looking up an ActivityPub object
377399
-------------------------------------------------

packages/init/src/action/configs.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function createInitData(): InitCommandData {
2121
messageQueue: "denokv",
2222
dryRun: false,
2323
allowNonEmpty: false,
24+
skipInstall: false,
2425
testMode: false,
2526
dir: "/tmp/example",
2627
initializer: {
@@ -166,6 +167,7 @@ async function createNpmInitData(dir: string): Promise<InitCommandData> {
166167
messageQueue: "in-process",
167168
dryRun: false,
168169
allowNonEmpty: false,
170+
skipInstall: false,
169171
testMode: false,
170172
dir,
171173
});
@@ -179,6 +181,7 @@ async function createNpmInitData(dir: string): Promise<InitCommandData> {
179181
messageQueue: "in-process",
180182
dryRun: false,
181183
allowNonEmpty: false,
184+
skipInstall: false,
182185
testMode: false,
183186
dir,
184187
initializer,
@@ -199,6 +202,7 @@ async function createNuxtNpmInitData(dir: string): Promise<InitCommandData> {
199202
messageQueue: "in-process",
200203
dryRun: false,
201204
allowNonEmpty: false,
205+
skipInstall: false,
202206
testMode: false,
203207
dir,
204208
});
@@ -212,6 +216,7 @@ async function createNuxtNpmInitData(dir: string): Promise<InitCommandData> {
212216
messageQueue: "in-process",
213217
dryRun: false,
214218
allowNonEmpty: false,
219+
skipInstall: false,
215220
testMode: false,
216221
dir,
217222
initializer,

packages/init/src/action/mod.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
noticeHowToRun,
1212
noticeOptions,
1313
noticePrecommand,
14+
noticeSkippedInstall,
1415
} from "./notice.ts";
1516
import {
1617
assertNoGeneratedFileConflicts,
@@ -23,6 +24,7 @@ import {
2324
hasCommand,
2425
installDependencies,
2526
isDry,
27+
isSkipInstall,
2628
runPrecommand,
2729
} from "./utils.ts";
2830

@@ -39,7 +41,9 @@ import {
3941
* - If dry run, executes `handleDryRun`.
4042
* - Otherwise, executes `handleHydRun`.
4143
* 7. Recommends configuration environment via `recommendConfigEnv`.
42-
* 8. Shows how to run the project via `noticeHowToRun`.
44+
* 8. If installation was skipped and not a dry run, prints how to install
45+
* dependencies manually via `noticeSkippedInstall`.
46+
* 9. Shows how to run the project via `noticeHowToRun`.
4347
*/
4448
const runInit = (options: InitCommand) =>
4549
pipe(
@@ -52,6 +56,7 @@ const runInit = (options: InitCommand) =>
5256
when(isDry, handleDryRun),
5357
unless(isDry, handleHydRun),
5458
tap(recommendConfigEnv),
59+
tap(unless(isDry, when(isSkipInstall, noticeSkippedInstall))),
5560
tap(noticeHowToRun),
5661
);
5762

@@ -76,5 +81,5 @@ const handleHydRun = (data: InitCommandData) =>
7681
tap(assertNoGeneratedFileConflicts),
7782
tap(when(hasCommand, runPrecommand)),
7883
tap(patchFiles),
79-
tap(installDependencies),
84+
tap(unless(isSkipInstall, installDependencies)),
8085
);

packages/init/src/action/notice.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ ${instruction}
117117
Start by editing the ${text(federationFile)} file to define your federation!
118118
`;
119119

120+
/** Prints a notice that dependency installation was skipped and how to install them manually. */
121+
export const noticeSkippedInstall = (
122+
{ packageManager }: InitCommandData,
123+
) =>
124+
printMessage`
125+
Dependencies were not installed. Run ${
126+
text(`${packageManager} install`)
127+
} in the project directory to install them.
128+
`;
129+
120130
/**
121131
* Returns an error handler that prints a formatted error message when
122132
* a dependency installation command fails, then throws.

packages/init/src/action/patch.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ function createInitData(
7474
messageQueue: "in-process",
7575
dryRun: false,
7676
allowNonEmpty,
77+
skipInstall: false,
7778
testMode: false,
7879
dir,
7980
initializer: {

packages/init/src/action/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import type { InitCommandData } from "../types.ts";
55
/** Returns `true` if the current run is in dry-run mode. */
66
export const isDry = ({ dryRun }: InitCommandData) => dryRun;
77

8+
/** Returns `true` if the current run skips dependency installation. */
9+
export const isSkipInstall = (
10+
{ skipInstall }: Pick<InitCommandData, "skipInstall">,
11+
) => skipInstall;
12+
813
/** Returns `true` if the framework initializer has a precommand to execute. */
914
export const hasCommand = (data: InitCommandData) => !!data.initializer.command;
1015

packages/init/src/command.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ export const initOptions = object("Initialization options", {
7777
description:
7878
message`Allow initializing in a non-empty directory when the selected framework scaffolder supports it, failing if any generated file already exists.`,
7979
}),
80+
skipInstall: option("--skip-install", {
81+
description:
82+
message`Skip installing dependencies after scaffolding the project.`,
83+
}),
8084
});
8185

8286
/**

packages/init/src/package.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ test(
4646
dir: packageDir,
4747
dryRun: true,
4848
allowNonEmpty: false,
49+
skipInstall: false,
4950
kvStore: "in-memory",
5051
messageQueue: "in-process",
5152
packageManager: "bun",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { parse } from "@optique/core/parser";
2+
import { ok, strictEqual } from "node:assert/strict";
3+
import test from "node:test";
4+
import { isSkipInstall } from "./action/utils.ts";
5+
import { initOptions } from "./command.ts";
6+
7+
test("initOptions parses --skip-install as true", () => {
8+
const result = parse(initOptions, ["--skip-install"]);
9+
ok(result.success);
10+
if (result.success) {
11+
strictEqual(result.value.skipInstall, true);
12+
}
13+
});
14+
15+
test("initOptions defaults skipInstall to false when the flag is absent", () => {
16+
const result = parse(initOptions, []);
17+
ok(result.success);
18+
if (result.success) {
19+
strictEqual(result.value.skipInstall, false);
20+
}
21+
});
22+
23+
test("isSkipInstall mirrors the skipInstall field", () => {
24+
strictEqual(isSkipInstall({ skipInstall: false }), false);
25+
strictEqual(isSkipInstall({ skipInstall: true }), true);
26+
});

0 commit comments

Comments
 (0)