Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ To be released.
- Added `SqliteMessageQueue.getDepth()` for reporting queued, ready, and
delayed message counts. [[#735], [#748]]

### @fedify/init

- Added a `--skip-install` option to `fedify init` that skips automatic
dependency installation after scaffolding. This is useful for CI
environments, monorepo workspaces that install dependencies from the
root, or when you want to inspect the generated files before
installing. [[#720], [#776] by fru1tworld]

[#720]: https://github.com/fedify-dev/fedify/issues/720
[#776]: https://github.com/fedify-dev/fedify/pull/776

### Claude Code plugin

- Added a Claude Code plugin at *claude-plugin/*, installable with:
Expand Down
22 changes: 22 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,28 @@ the selected framework scaffolder accepts them. Some scaffolders, such as
own confirmation prompt, while a freshly initialized *.git* directory remains
acceptable.

### `--skip-install`: Skip installing dependencies

*This option is available since Fedify 2.3.0.*

By default, `fedify init` runs the selected package manager's install command
right after scaffolding the project. The `--skip-install` option scaffolds the
files without running install, which is useful when:

- installation is handled separately in a CI pipeline;
- the new project lives inside a monorepo whose dependencies are installed
from the workspace root; or
- you want to inspect the generated files before installing.

~~~~ sh
fedify init my-fedify-project --skip-install
~~~~

After scaffolding, `fedify init` prints the command to run to install
dependencies later. Other steps such as creating files, applying patches, and
running the framework-specific scaffolder (e.g., *create-next-app*) still
happen as usual; only the final install step is skipped.


`fedify lookup`: Looking up an ActivityPub object
-------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions packages/init/src/action/configs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function createInitData(): InitCommandData {
messageQueue: "denokv",
dryRun: false,
allowNonEmpty: false,
skipInstall: false,
testMode: false,
dir: "/tmp/example",
initializer: {
Expand Down Expand Up @@ -166,6 +167,7 @@ async function createNpmInitData(dir: string): Promise<InitCommandData> {
messageQueue: "in-process",
dryRun: false,
allowNonEmpty: false,
skipInstall: false,
testMode: false,
dir,
});
Expand All @@ -179,6 +181,7 @@ async function createNpmInitData(dir: string): Promise<InitCommandData> {
messageQueue: "in-process",
dryRun: false,
allowNonEmpty: false,
skipInstall: false,
testMode: false,
dir,
initializer,
Expand All @@ -199,6 +202,7 @@ async function createNuxtNpmInitData(dir: string): Promise<InitCommandData> {
messageQueue: "in-process",
dryRun: false,
allowNonEmpty: false,
skipInstall: false,
testMode: false,
dir,
});
Expand All @@ -212,6 +216,7 @@ async function createNuxtNpmInitData(dir: string): Promise<InitCommandData> {
messageQueue: "in-process",
dryRun: false,
allowNonEmpty: false,
skipInstall: false,
testMode: false,
dir,
initializer,
Expand Down
7 changes: 6 additions & 1 deletion packages/init/src/action/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
noticeHowToRun,
noticeOptions,
noticePrecommand,
noticeSkippedInstall,
} from "./notice.ts";
import {
assertNoGeneratedFileConflicts,
Expand All @@ -23,6 +24,7 @@ import {
hasCommand,
installDependencies,
isDry,
isSkipInstall,
runPrecommand,
} from "./utils.ts";

Expand All @@ -40,6 +42,8 @@ import {
* - Otherwise, executes `handleHydRun`.
* 7. Recommends configuration environment via `recommendConfigEnv`.
* 8. Shows how to run the project via `noticeHowToRun`.
* 9. If installation was skipped and not a dry run, prints how to install
* dependencies manually via `noticeSkippedInstall`.
*/
const runInit = (options: InitCommand) =>
pipe(
Expand All @@ -53,6 +57,7 @@ const runInit = (options: InitCommand) =>
unless(isDry, handleHydRun),
tap(recommendConfigEnv),
tap(noticeHowToRun),
tap(unless(isDry, when(isSkipInstall, noticeSkippedInstall))),
Comment on lines 59 to +60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The notice about skipped dependency installation should ideally appear before the "how to run" instructions. This provides a better user experience by informing the user that they need to perform an installation step before they attempt to run the project using the commands shown in the next step.

Suggested change
tap(noticeHowToRun),
tap(unless(isDry, when(isSkipInstall, noticeSkippedInstall))),
tap(unless(isDry, when(isSkipInstall, noticeSkippedInstall))),
tap(noticeHowToRun),

);

export default runInit;
Expand All @@ -76,5 +81,5 @@ const handleHydRun = (data: InitCommandData) =>
tap(assertNoGeneratedFileConflicts),
tap(when(hasCommand, runPrecommand)),
tap(patchFiles),
tap(installDependencies),
tap(unless(isSkipInstall, installDependencies)),
);
10 changes: 10 additions & 0 deletions packages/init/src/action/notice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ ${instruction}
Start by editing the ${text(federationFile)} file to define your federation!
`;

/** Prints a notice that dependency installation was skipped and how to install them manually. */
export const noticeSkippedInstall = (
{ packageManager }: InitCommandData,
) =>
printMessage`
Dependencies were not installed. Run ${
text(`${packageManager} install`)
} in the project directory to install them.
`;

/**
* Returns an error handler that prints a formatted error message when
* a dependency installation command fails, then throws.
Expand Down
1 change: 1 addition & 0 deletions packages/init/src/action/patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function createInitData(
messageQueue: "in-process",
dryRun: false,
allowNonEmpty,
skipInstall: false,
testMode: false,
dir,
initializer: {
Expand Down
5 changes: 5 additions & 0 deletions packages/init/src/action/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import type { InitCommandData } from "../types.ts";
/** Returns `true` if the current run is in dry-run mode. */
export const isDry = ({ dryRun }: InitCommandData) => dryRun;

/** Returns `true` if the current run skips dependency installation. */
export const isSkipInstall = (
{ skipInstall }: Pick<InitCommandData, "skipInstall">,
) => skipInstall;

/** Returns `true` if the framework initializer has a precommand to execute. */
export const hasCommand = (data: InitCommandData) => !!data.initializer.command;

Expand Down
4 changes: 4 additions & 0 deletions packages/init/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export const initOptions = object("Initialization options", {
description:
message`Allow initializing in a non-empty directory when the selected framework scaffolder supports it, failing if any generated file already exists.`,
}),
skipInstall: option("--skip-install", {
description:
message`Skip installing dependencies after scaffolding the project.`,
}),
});

/**
Expand Down
1 change: 1 addition & 0 deletions packages/init/src/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ test(
dir: packageDir,
dryRun: true,
allowNonEmpty: false,
skipInstall: false,
kvStore: "in-memory",
messageQueue: "in-process",
packageManager: "bun",
Expand Down
26 changes: 26 additions & 0 deletions packages/init/src/skip-install.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { parse } from "@optique/core/parser";
import { ok, strictEqual } from "node:assert/strict";
import test from "node:test";
import { isSkipInstall } from "./action/utils.ts";
import { initOptions } from "./command.ts";

test("initOptions parses --skip-install as true", () => {
const result = parse(initOptions, ["--skip-install"]);
ok(result.success);
if (result.success) {
strictEqual(result.value.skipInstall, true);
}
});

test("initOptions defaults skipInstall to false when the flag is absent", () => {
const result = parse(initOptions, []);
ok(result.success);
if (result.success) {
strictEqual(result.value.skipInstall, false);
}
});

test("isSkipInstall mirrors the skipInstall field", () => {
strictEqual(isSkipInstall({ skipInstall: false }), false);
strictEqual(isSkipInstall({ skipInstall: true }), true);
});
4 changes: 4 additions & 0 deletions packages/init/src/webframeworks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ test("Nitro template loads LogTape during server startup", async () => {
testMode: false,
dryRun: true,
allowNonEmpty: false,
skipInstall: false,
});

ok(files);
Expand All @@ -38,6 +39,7 @@ test("Next.js template loads LogTape through instrumentation", async () => {
testMode: false,
dryRun: true,
allowNonEmpty: false,
skipInstall: false,
});

ok(files);
Expand All @@ -61,6 +63,7 @@ test("Astro template loads LogTape through middleware", async () => {
testMode: false,
dryRun: true,
allowNonEmpty: false,
skipInstall: false,
});

ok(files);
Expand All @@ -81,6 +84,7 @@ test("SolidStart template loads LogTape through middleware", async () => {
testMode: false,
dryRun: true,
allowNonEmpty: false,
skipInstall: false,
});

ok(files);
Expand Down