Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/tangy-deer-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'sv': minor
---

- feat(cli): remove `--no-preconditions` option from `sv add`
- feat(cli): add `--no-git-check` option to `sv add`. With this flag, even if some files are dirty, no prompt will be shown
2 changes: 1 addition & 1 deletion documentation/docs/20-commands/20-sv-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You can select multiple space-separated add-ons from [the list below](#Official-
## Options

- `-C`, `--cwd` — path to the root of your Svelte(Kit) project
- `--no-preconditions` — skip checking preconditions <!-- TODO what does this mean? -->
- `--no-git-check` — even if some files are dirty, no prompt will be shown
- `--install` — installs dependencies with a specified package manager
- `--no-install` — prevents installing dependencies

Expand Down
54 changes: 27 additions & 27 deletions packages/cli/commands/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
installOption,
packageManagerPrompt
} from '../../utils/package-manager.ts';
import { getGlobalPreconditions } from './preconditions.ts';
import { verifyCleanWorkingDirectory, verifyUnsupportedAddons } from './verifiers.ts';
import { type AddonMap, applyAddons, setupAddons } from '../../lib/install.ts';

const aliases = officialAddons.map((c) => c.alias).filter((v) => v !== undefined);
Expand All @@ -36,7 +36,7 @@ const AddonsSchema = v.array(v.string());
const OptionsSchema = v.strictObject({
cwd: v.string(),
install: v.union([v.boolean(), v.picklist(AGENT_NAMES)]),
preconditions: v.boolean(),
gitCheck: v.boolean(),
community: v.optional(v.union([AddonsSchema, v.boolean()])),
addons: v.record(v.string(), v.optional(v.array(v.string())))
});
Expand Down Expand Up @@ -81,7 +81,7 @@ export const add = new Command('add')
return prev;
})
.option('-C, --cwd <path>', 'path to working directory', defaultCwd)
.option('--no-preconditions', 'skip validating preconditions')
.option('--no-git-check', 'even if some files are dirty, no prompt will be shown')
.option('--no-install', 'skip installing dependencies')
.addOption(installOption)
//.option('--community [add-on...]', 'community addons to install')
Expand Down Expand Up @@ -445,33 +445,33 @@ export async function runAddCommand(
}
}

// run precondition checks
if (options.preconditions && selectedAddons.length > 0) {
// add global checks
const addons = selectedAddons.map(({ addon }) => addon);
const { preconditions } = getGlobalPreconditions(options.cwd, addons, addonSetupResults);

const fails: Array<{ name: string; message?: string }> = [];
for (const condition of preconditions) {
const { message, success } = await condition.run();
if (!success) fails.push({ name: condition.name, message });
}
// run verifications
const addons = selectedAddons.map(({ addon }) => addon);
const verifications = [
...verifyCleanWorkingDirectory(options.cwd, options.gitCheck),
...verifyUnsupportedAddons(addons, addonSetupResults)
];

const fails: Array<{ name: string; message?: string }> = [];
for (const verification of verifications) {
const { message, success } = await verification.run();
if (!success) fails.push({ name: verification.name, message });
}

if (fails.length > 0) {
const message = fails
.map(({ name, message }) => pc.yellow(`${name} (${message})`))
.join('\n- ');
if (fails.length > 0) {
const message = fails
.map(({ name, message }) => pc.yellow(`${name} (${message})`))
.join('\n- ');

p.note(`- ${message}`, 'Preconditions not met', { format: (line) => line });
p.note(`- ${message}`, 'Verifications not met', { format: (line) => line });

const force = await p.confirm({
message: 'Preconditions failed. Do you wish to continue?',
initialValue: false
});
if (p.isCancel(force) || !force) {
p.cancel('Operation cancelled.');
process.exit(1);
}
const force = await p.confirm({
message: 'Verifications failed. Do you wish to continue?',
initialValue: false
});
if (p.isCancel(force) || !force) {
p.cancel('Operation cancelled.');
process.exit(1);
}
}

Expand Down
56 changes: 0 additions & 56 deletions packages/cli/commands/add/preconditions.ts

This file was deleted.

62 changes: 62 additions & 0 deletions packages/cli/commands/add/verifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import type { AddonSetupResult, AddonWithoutExplicitArgs, Verification } from '@sveltejs/cli-core';
import { UnsupportedError } from '../../utils/errors.ts';

export function verifyCleanWorkingDirectory(cwd: string, gitCheck: boolean) {
const verifications: Verification[] = [];

if (gitCheck) {
verifications.push({
name: 'clean working directory',
run: async () => {
try {
// If a user has pending git changes the output of the following command will list
// all files that have been added/modified/deleted and thus the output will not be empty.
// In case the output of the command below is an empty text, we can safely assume
// there are no pending changes. If the below command is run outside of a git repository,
// git will exit with a failing exit code, which will trigger the catch statement.
// also see https://remarkablemark.org/blog/2017/10/12/check-git-dirty/#git-status
const asyncExec = promisify(exec);
const { stdout } = await asyncExec('git status --short', {
cwd
});

if (stdout) {
return { success: false, message: 'Found modified files' };
}

return { success: true, message: undefined };
} catch {
return { success: true, message: 'Not a git repository' };
}
}
});
}

return verifications;
}

export function verifyUnsupportedAddons(
addons: AddonWithoutExplicitArgs[],
addonSetupResult: Record<string, AddonSetupResult>
) {
const verifications: Verification[] = [];

verifications.push({
name: 'unsupported add-ons',
run: () => {
const reasons = addons.flatMap((a) =>
addonSetupResult[a.id].unsupported.map((reason) => ({ id: a.id, reason }))
);

if (reasons.length === 0) {
return { success: true, message: undefined };
}

throw new UnsupportedError(reasons);
}
});

return verifications;
}
2 changes: 1 addition & 1 deletion packages/cli/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ async function createProject(cwd: ProjectPath, options: Options) {
{
cwd: projectPath,
install: options.install,
preconditions: false,
gitCheck: false,
community: [],
addons: {}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/addon/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function defineAddonOptions<const Args extends OptionDefinition>(options:

type MaybePromise<T> = Promise<T> | T;

export type Precondition = {
export type Verification = {
name: string;
run: () => MaybePromise<{ success: boolean; message: string | undefined }>;
};
Loading