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
3 changes: 3 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ The examples below reflect the command definitions in `src/index.ts` and the gen
| `emulsify component eject-templates [type]` | | Write editable built-in component templates into the project. |
| `emulsify cache clear` | | Clear locally cached system repositories. |

`emulsify components` is an alias for the canonical `emulsify component`
command group, and accepts the same component subcommands and options.

## `init`

```bash
Expand Down
11 changes: 10 additions & 1 deletion docs/project-initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ web/app/themes/my-theme

If the target already exists, initialization stops with an error and does not overwrite the directory.

## Failed Initialization And Cleanup

Init atomically creates the target directory before cloning, so it only treats a target created by that command run as owned. If the target already exists or another process creates it during preflight, initialization stops without removing it. If cloning, configuration, dependency installation, the starter hook, or Git metadata cleanup then fails, the error identifies the failed phase and the CLI removes the incomplete target recursively so the same init command can be retried.

If automatic cleanup also fails, the error includes the target path and asks you to remove it manually before retrying. A target that existed before init started is never removed; the preflight occupied-target check stops before cloning begins.

## Machine Names

If `--machineName` is omitted, the CLI derives one from the project name by removing non-alphanumeric characters, replacing spaces, and lowercasing the result.
Expand Down Expand Up @@ -171,14 +177,17 @@ If `--checkout` is omitted, the starter repository default branch is cloned.

After init, the generated `project.emulsify.json` stores a concrete platform value. It never stores compatibility expressions such as `drupal || wordpress` in `project.platform`.

When a starter already contains `project.emulsify.json`, init preserves its project defaults and other top-level configuration. The requested platform, project name, machine name, and starter repository replace the starter's template identity. This lets starter-owned settings such as Drupal Single Directory Component output remain enabled.

For a Drupal init, the file looks like this:

```json
{
"project": {
"platform": "drupal",
"name": "My Theme",
"machineName": "my_theme"
"machineName": "my_theme",
"singleDirectoryComponents": true
},
"starter": {
"repository": "https://github.com/emulsify-ds/emulsify-drupal-starter"
Expand Down
5 changes: 3 additions & 2 deletions docs/systems.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ For a built-in system, the command:
4. Clones the system into the local Emulsify cache.
5. Reads and validates `system.emulsify.json` from the cached system.
6. Selects the reviewed component set in guided mode, or resolves the best compatible variant for `project.platform` in direct mode. `--variant` selects an exact expression in direct mode.
7. Selects essential or all components.
7. Selects essential components and their declared dependencies, or all components.
8. Presents and confirms the review in guided mode.
9. Writes `system` and `variant` entries into `project.emulsify.json`.
10. Installs the selected components and variant-level general files and directories.
Expand All @@ -111,7 +111,8 @@ emulsify system install emulsify-ui-kit

An explicit built-in name bypasses the wizard. This is the form to use in a
script or CI job. It selects the best compatible component set automatically and
installs only essential components unless flags override those choices.
installs only essential components and their declared dependencies unless flags
override those choices.

Use `--all` to install every component in the selected variant during system installation:

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@emulsify/cli",
"productName": "Emulsify CLI",
"version": "2.4.0",
"version": "2.4.1",
"description": "Build and use component systems in Drupal, WordPress, or standalone front ends.",
"repository": "git@github.com:emulsify-ds/emulsify-cli.git",
"author": "Patrick Coffey <patrickcoffey48@gmail.com>",
Expand Down
14 changes: 14 additions & 0 deletions src/handlers/componentCreate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ describe('componentCreate', () => {
);
});

it('checks for a configured system before prompting for a missing name', async () => {
getEmulsifyConfigMock.mockResolvedValueOnce({
...projectConfig,
system: undefined,
});

await expect(componentCreate(undefined)).rejects.toThrow(
'You must select and install a system before you can create components.',
);

expect(inputMock).not.toHaveBeenCalled();
expect(generateComponentMock).not.toHaveBeenCalled();
});

it('throws when no variant is configured', async () => {
getEmulsifyConfigMock.mockResolvedValueOnce({
...projectConfig,
Expand Down
25 changes: 15 additions & 10 deletions src/handlers/componentCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,13 @@ export default async function componentCreate(
name: string | void,
options: CreateComponentHandlerOptions = {},
): Promise<void> {
const componentName = name?.trim()
? name
: await runPrompt({
prompt: () =>
input({
message: 'Component name:',
validate: validateComponentName,
}),
nonInteractive: { error: MISSING_COMPONENT_NAME_ERROR },
});
const providedComponentName = name?.trim() ? name : undefined;

// Missing prompt values can be rejected before loading or refreshing the
// configured system, keeping CI failures fast and offline.
if (!providedComponentName) {
requireInteractiveTerminal(MISSING_COMPONENT_NAME_ERROR);
}
if (!options.type && !options.format) {
requireInteractiveTerminal(MISSING_COMPONENT_TYPE_ERROR);
}
Expand All @@ -65,6 +59,17 @@ export default async function componentCreate(
},
);

const componentName =
providedComponentName ??
(await runPrompt({
prompt: () =>
input({
message: 'Component name:',
validate: validateComponentName,
}),
nonInteractive: { error: MISSING_COMPONENT_NAME_ERROR },
}));

try {
await generateComponent(
variantConf,
Expand Down
Loading
Loading