1: <?php
2:
3: namespace LaravelUi5\Core\Commands;
4:
5: use Illuminate\Filesystem\Filesystem;
6: use Illuminate\Support\Facades\File;
7: use Illuminate\Support\Str;
8: use JsonException;
9: use LaravelUi5\Core\Commands\Concerns\RunsUi5Build;
10: use LaravelUi5\Core\Infrastructure\Contracts\Ui5SourceOverrideStoreInterface;
11: use LaravelUi5\Core\Introspection\App\Ui5AppSource;
12: use LogicException;
13:
14: class GenerateUi5AppCommand extends BaseGenerator
15: {
16: use RunsUi5Build;
17:
18: protected $signature = 'ui5:app {name : The name of the ui5 app}
19: {--package-prefix=pragmatiqu : The composer package namespace prefix}
20: {--php-ns-prefix=Pragmatiqu : The namespace prefix for the php package}
21: {--js-ns-prefix=io.pragmatiqu : The JS namespace prefix}
22: {--create : Create a new module from scratch}
23: {--refresh : Overwrite existing files without confirmation}
24: {--vendor=Pragmatiqu IT GmbH : The vendor of the module}
25: {--auto-build : Run UI5 build before importing assets}';
26:
27: protected $description = 'Generate a Ui5App module from a UI5 frontend project';
28:
29: protected Filesystem $files;
30:
31: public function __construct(Filesystem $files)
32: {
33: parent::__construct();
34: $this->files = $files;
35: }
36:
37: /**
38: * @throws JsonException
39: */
40: public function handle(Ui5SourceOverrideStoreInterface $store): int
41: {
42: $appName = $this->argument('name');
43: $this->assertCamelCase('Ui5App', $appName);
44:
45: $jsNamespacePrefix = rtrim($this->option('js-ns-prefix'), '.');
46: $ui5AppFolderName = Str::kebab($appName);
47:
48: $conventionPaths = [
49: base_path("../ui5-{$ui5AppFolderName}/"),
50: base_path("../{$jsNamespacePrefix}.{$ui5AppFolderName}/"),
51: ];
52:
53: /** @var string $sourcePath */
54: $sourcePath = collect($conventionPaths)
55: ->first(fn($path) => File::exists($path));
56:
57: if (is_null($sourcePath)) {
58: throw new LogicException(
59: sprintf(
60: 'Source folder for UI5 app not found. Tried:\n - %s',
61: implode("\n - ", $conventionPaths)
62: )
63: );
64: }
65:
66: $targetPath = base_path("ui5/{$appName}/src/");
67: $className = "{$appName}App";
68: $moduleClassName = "{$appName}Module";
69: $phpNamespacePrefix = rtrim($this->option('php-ns-prefix'), '\\');
70: $phpNamespace = "{$phpNamespacePrefix}\\{$appName}";
71: $targetFile = "{$targetPath}{$className}.php";
72: $targetModuleFile = "{$targetPath}{$moduleClassName}.php";
73: $vendor = trim($this->option('vendor'));
74:
75: if ($this->output->isVerbose()) {
76: $this->info("sourcePath: {$sourcePath}");
77: $this->info("targetPath: {$targetPath}");
78: $this->info("className: {$className}");
79: $this->info("moduleClassName: {$moduleClassName}");
80: $this->info("phpNamespace: {$phpNamespace}");
81: $this->info("targetFile: {$targetFile}");
82: $this->info("targetModuleFile: {$targetModuleFile}");
83: }
84:
85: $create = $this->option('create');
86: $refresh = $this->option('refresh');
87: $exists = File::exists($targetFile);
88:
89: // Decision tree
90: if ($create && $exists) {
91: $this->components->error("Module already exists. Use --refresh to update.");
92: return self::FAILURE;
93: }
94:
95: if ($refresh && !$exists) {
96: $this->components->error("Module does not exist. Use --create to scaffold.");
97: return self::FAILURE;
98: }
99:
100: if (!$create && !$refresh) {
101: if ($exists) {
102: $this->components->info("Module already exists. Use --refresh to update.");
103: } else {
104: $this->components->info("Module does not exist. Use --create to scaffold.");
105: }
106: return self::FAILURE;
107: }
108:
109: if ($this->option('auto-build')) {
110: $this->runBuild($sourcePath);
111: }
112:
113: File::ensureDirectoryExists($targetPath);
114:
115: $source = Ui5AppSource::fromWorkspace(
116: path: $sourcePath,
117: vendor: $vendor,
118: isDev: true
119: );
120:
121: if (!$exists) {
122: // composer.json
123: $this->files->put("{$targetPath}../composer.json", $this->compileStub('composer.stub', [
124: 'packagePrefix' => $this->option('package-prefix'),
125: 'urlKey' => $ui5AppFolderName,
126: 'description' => $source->getDescriptor()->getDescription(),
127: 'namespace' => json_encode($phpNamespace),
128: ]));
129:
130: // ServiceProvider
131: $this->files->put("{$targetPath}/{$appName}ServiceProvider.php", $this->compileStub('ServiceProvider.stub', [
132: 'namespace' => $phpNamespace,
133: 'name' => $appName,
134: ]));
135:
136: // Module
137: $this->files->put("{$targetPath}/{$appName}Module.php", $this->compileStub('Ui5ModuleApp.stub', [
138: 'phpNamespace' => $phpNamespace,
139: 'class' => $className,
140: 'moduleClass' => $moduleClassName,
141: 'name' => json_encode($appName),
142: ]));
143:
144: // Manifest
145: $this->files->put("{$targetPath}/{$appName}Manifest.php", $this->compileStub('Ui5Manifest.stub', [
146: 'phpNamespace' => $phpNamespace,
147: 'class' => $appName
148: ]));
149: }
150:
151: // app
152: $this->files->put($targetFile, $this->compileStub('Ui5App.stub', [
153: 'name' => $appName,
154: 'namespace' => $phpNamespace,
155: 'class' => $className,
156: 'ui5Namespace' => $source->getDescriptor()->getNamespace(),
157: 'appVersion' => $source->getDescriptor()->getVersion(),
158: 'title' => addslashes($source->getDescriptor()->getTitle()),
159: 'description' => addslashes($source->getDescriptor()->getDescription()),
160: 'bootstrapAttributes' => var_export($source->getBootstrap()->getAttributes(), true),
161: 'resourceNamespaces' => var_export($source->getBootstrap()->getResourceNamespaces(), true),
162: 'inlineScript' => $source->getBootstrap()->getInlineScript(),
163: 'inlineCss' => $source->getBootstrap()->getInlineCss(),
164: 'vendor' => $vendor,
165: ]));
166:
167: // dist assets
168: $i18nFiles = collect(File::files($sourcePath . 'dist/i18n'))
169: ->filter(fn($f) => Str::endsWith($f->getFilename(), '.properties'))
170: ->map(fn($f) => 'i18n/' . $f->getFilename())
171: ->all();
172:
173: $staticFiles = collect([
174: 'manifest.json',
175: 'Component-preload.js',
176: 'Component-preload.js.map',
177: 'Component-dbg.js',
178: 'Component-dbg.js.map',
179: 'i18n/i18n.properties',
180: ]);
181:
182: $assets = $staticFiles->merge($i18nFiles)->unique()->values()->all();
183:
184: $this->copyDistAssets($sourcePath . 'dist', $targetPath, $assets);
185:
186: // Register source
187: $store->put("$phpNamespace\\$moduleClassName", $sourcePath);
188:
189: $operation = $exists ? 'Updated' : 'Created';
190: $this->components->success("{$operation} Ui5App module `{$appName}`");
191:
192: return self::SUCCESS;
193: }
194: }
195: