Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a54f8ca
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
f3d4219
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
9dabe0a
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
3f0de7a
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
390003e
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
af5f1a9
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
4a4340f
refactoring
N1ebieski Sep 19, 2025
25f4303
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
3578992
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 19, 2025
02f49c1
Windows support
N1ebieski Sep 19, 2025
53e6459
refactoring
N1ebieski Sep 20, 2025
ae02a4e
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 20, 2025
2eca810
refactoring
N1ebieski Sep 20, 2025
69d74ab
fix for windows submenu command selection
N1ebieski Sep 20, 2025
d6e28d0
refactoring
N1ebieski Sep 20, 2025
29f09ce
Add support for default callback
N1ebieski Sep 20, 2025
ddb8128
fix for livewire output path
N1ebieski Sep 20, 2025
15372c6
Add support for ArgumentType.NamespaceOrPath
N1ebieski Sep 20, 2025
410b372
format
N1ebieski Sep 20, 2025
26783ee
refactoring
N1ebieski Sep 20, 2025
85f8b88
remove numbers
N1ebieski Sep 20, 2025
ff4bc8f
replace EndSelection enum with const
N1ebieski Sep 20, 2025
abc9a16
rename callbackChoice to optionsChoice
N1ebieski Sep 20, 2025
5cd8710
refactoring enums to types
N1ebieski Sep 20, 2025
963afe6
refactoring
N1ebieski Sep 20, 2025
2dbb035
improvement for multi-root workspaces
N1ebieski Sep 21, 2025
52db2a4
fix for command palette namespaces
N1ebieski Sep 21, 2025
eef7d25
typo
N1ebieski Sep 21, 2025
84b1879
typo
N1ebieski Sep 21, 2025
d31c799
typo
N1ebieski Sep 21, 2025
474b6ee
replace regex to replaceAll
N1ebieski Sep 21, 2025
ab2d50d
syntax
N1ebieski Sep 21, 2025
5f4310c
add ucfirst helper
N1ebieski Sep 23, 2025
cff8350
Merge branch 'Fix-for-PHP-Command-for-Pint-on-Windows-#62' into Integ…
N1ebieski Sep 23, 2025
2441636
php path for artisan command at the beginning
N1ebieski Sep 23, 2025
ec86f0b
Merge branch 'Fix-for-PHP-Command-for-Pint-on-Windows-#62' into Integ…
N1ebieski Sep 23, 2025
94537df
Integration artisan make commands with VSCode explorer/context
N1ebieski Sep 23, 2025
09d239e
Merge branch 'Fix-for-PHP-Command-for-Pint-on-Windows-#62' into Integ…
N1ebieski Sep 23, 2025
c27eb7e
refactoring
N1ebieski Sep 26, 2025
af9a7e1
move laravel artisan make commands to the explorer/context submenu
N1ebieski Oct 8, 2025
0feb5d7
change laravel.artisan.make command title
N1ebieski Oct 8, 2025
5e1f318
Refactor artisan make commands to repository
N1ebieski Oct 8, 2025
1aba406
improvement view and markdown value
N1ebieski Oct 8, 2025
2d9f272
rename a repository file to artisanMakeCommands
N1ebieski Oct 8, 2025
3f76c35
Merge branch 'Fix-for-PHP-Command-for-Pint-on-Windows-#62' into Integ…
N1ebieski Oct 8, 2025
47028b2
remove double quotes from phpPath
N1ebieski Oct 8, 2025
da6f374
replace artisanPath with artisan
N1ebieski Oct 8, 2025
4843aa0
Integration artisan make commands with VSCode explorer/context
N1ebieski Nov 10, 2025
ab8e8e0
Integration artisan make commands with VSCode explorer/context
N1ebieski Nov 10, 2025
6e0c747
Fixes N1ebieski/vs-code-extension#61 Integration artisan make command…
N1ebieski Dec 28, 2025
54665e8
Merge branch 'main' into Integration-artisan-make-commands-with-VSCod…
TitasGailius Jan 6, 2026
b5da755
Refactor
TitasGailius Jan 8, 2026
9e8339f
Add --command option
TitasGailius Jan 8, 2026
69dfdb1
formatting
TitasGailius Jan 8, 2026
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
447 changes: 447 additions & 0 deletions package.json

Large diffs are not rendered by default.

252 changes: 252 additions & 0 deletions src/artisan/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import * as vscode from "vscode";
import path from "path";

import { Argument, ArgumentType, Command, Option } from "./types";
import { getNamespace } from "@src/commands/generateNamespace";
import { escapeNamespace } from "@src/support/util";

const EndSelection = "End Selection";

const getValueForArgumentType = async (
value: string,
argumentType: ArgumentType | undefined,
workspaceFolder: vscode.WorkspaceFolder,
uri: vscode.Uri,
): Promise<string> => {
switch (argumentType) {
case "namespaceOrPath":
case "namespace":
// User can input a relative path, for example: NewFolder\NewFile
// or NewFolder/NewFile, so we need to convert it to a new Uri
const newUri = vscode.Uri.joinPath(uri, value);

const fileName = path.parse(newUri.fsPath).name;

// Always try to get the full namespace because it supports
// projects with modular architecture
let namespace = await getNamespace(workspaceFolder, newUri);

if (!namespace && argumentType === "namespaceOrPath") {
return getValueForArgumentType(
value,
"path",
workspaceFolder,
uri,
);
}

namespace = namespace ? (namespace += `\\${fileName}`) : value;

return escapeNamespace(namespace.replaceAll("/", "\\").trim());

case "path":
// OS path separators
return path.normalize(value.replaceAll("\\", "/")).trim();

default:
return value.trim();
}
};

const validateInput = (input: string, field: string): boolean => {
if (input === "") {
vscode.window.showWarningMessage(`${field} is required`);

return false;
}

if (/\s/.test(input)) {
vscode.window.showWarningMessage(`${field} cannot contain spaces`);

return false;
}

return true;
};

const getUserArguments = async (
commandArguments: Argument[],
workspaceFolder: vscode.WorkspaceFolder,
uri: vscode.Uri,
): Promise<Record<string, string> | undefined> => {
const userArguments: Record<string, string> = {};

for (const argument of commandArguments) {
let input = undefined;

while (!input) {
input = await vscode.window.showInputBox({
prompt: argument.description,
});

// Exit when the user press ESC
if (input === undefined) {
return;
}

if (!validateInput(input, `Argument ${argument.name}`)) {
input = undefined;
}
}

userArguments[argument.name] = await getValueForArgumentType(
input,
argument.type,
workspaceFolder,
uri,
);
}

return userArguments;
};

const getArgumentsAsString = (userArguments: Record<string, string>) =>
Object.values(userArguments).join(" ");

const getUserOptions = async (
commandOptions: Option[] | undefined,
userArguments: Record<string, string>,
): Promise<Record<string, string | undefined> | undefined> => {
const userOptions: Record<string, string | undefined> = {};

if (!commandOptions?.length) {
return userOptions;
}

let pickOptions = commandOptions.map((option) => ({
label: `${option.name} ${option.description}`,
command: option.name,
excludeIf: option.excludeIf,
}));

while (true) {
const optionsAsString = getOptionsAsString(userOptions);

const choice = await vscode.window.showQuickPick(
[
{
label: EndSelection,
command: EndSelection,
exclude: undefined,
},
...pickOptions,
],
{
canPickMany: false,
placeHolder:
optionsAsString || "Select an option or end selection.",
},
);

// Exit when the user press ESC
if (choice === undefined) {
return;
}

if (choice.command === EndSelection) {
break;
}

let value = undefined;

const option = commandOptions.find(
(option) => option.name === choice.command,
);

if (option?.type === "select" && option?.options) {
const optionsChoice = await vscode.window.showQuickPick(
Object.entries(option.options()).map(([key, value]) => ({
label: key,
command: value,
})),
);

// Once again if the user cancels the selection by pressing ESC
if (optionsChoice === undefined) {
continue;
}

value = optionsChoice.command;
}

if (option?.type === "input") {
let input = undefined;

while (!input) {
let _default = undefined;

if (typeof option.default === "string") {
_default = option.default;
}

if (typeof option.default === "function") {
_default = option.default(...Object.values(userArguments));
}

input = await vscode.window.showInputBox({
prompt: option.description,
value: _default ?? "",
});

// Exit when the user press ESC
if (input === undefined) {
break;
}

if (!validateInput(input, `Value for ${option.name}`)) {
input = undefined;
}
}

// Once again if the user cancels the selection by pressing ESC
if (input === undefined) {
continue;
}

value = input;
}

userOptions[choice.command] = value ?? choice.command;

pickOptions = pickOptions.filter(
(option) =>
option.command !== choice.command &&
!option.excludeIf?.includes(choice.command),
);

if (!pickOptions.length) {
break;
}
}

return userOptions;
};

const getOptionsAsString = (userOptions: Record<string, string | undefined>) =>
Object.entries(userOptions)
.map(([key, value]) => (key !== value ? `${key}=${value}` : key))
.join(" ");

export const buildArtisanCommand = async (
command: Command,
uri: vscode.Uri,
workspaceFolder: vscode.WorkspaceFolder,
): Promise<string | undefined> => {
const userArguments = await getUserArguments(
command.arguments,
workspaceFolder,
uri,
);

if (!userArguments) {
return;
}

const userOptions = await getUserOptions(command.options, userArguments);

if (!userOptions) {
return;
}

return `${command.name} ${getArgumentsAsString(userArguments)} ${getOptionsAsString(userOptions)}`;
};
18 changes: 18 additions & 0 deletions src/artisan/commands/CastMakeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Command } from "../types";

export const CastMakeCommand: Command = {
name: "make:cast",
arguments: [
{
name: "name",
type: "namespace",
description: "The name of the cast class",
},
],
options: [
{
name: "--inbound",
description: "Generate an inbound cast class",
},
],
};
14 changes: 14 additions & 0 deletions src/artisan/commands/ChannelMakeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Command } from "../types";
import { forceOption } from "../options";

export const ChannelMakeCommand: Command = {
name: "make:channel",
arguments: [
{
name: "name",
type: "namespace",
description: "The name of the channel",
},
],
options: [forceOption],
};
18 changes: 18 additions & 0 deletions src/artisan/commands/ClassMakeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Command } from "../types";

export const ClassMakeCommand: Command = {
name: "make:class",
arguments: [
{
name: "name",
type: "namespace",
description: "The name of the class",
},
],
options: [
{
name: "--invokable",
description: "Generate a single method, invokable class",
},
],
};
23 changes: 23 additions & 0 deletions src/artisan/commands/CommandMakeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Command } from "../types";
import { forceOption, testOptions } from "../options";

export const CommandMakeCommand: Command = {
name: "make:command",
arguments: [
{
name: "name",
type: "namespace",
description: "The name of the command",
},
],
options: [
{
name: "--command",
type: "input",
description:
"The terminal command that will be used to invoke the class",
},
...testOptions,
forceOption,
],
};
34 changes: 34 additions & 0 deletions src/artisan/commands/ComponentMakeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Command } from "../types";
import { forceOption, testOptions } from "../options";

export const ComponentMakeCommand: Command = {
name: "make:component",
arguments: [
{
name: "name",
type: "namespaceOrPath",
description: "The name of the component",
},
],
options: [
{
name: "--path",
type: "input",
default: "components",
description:
"The location where the component view should be created",
},
{
name: "--inline",
description: "Create a component that renders an inline view",
excludeIf: ["--view"],
},
{
name: "--view",
description: "Create an anonymous component with only a view",
excludeIf: ["--inline"],
},
...testOptions,
forceOption,
],
};
Loading