create-stylus 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/cli.js +658 -0
  2. package/dist/cli.js.map +1 -0
  3. package/package.json +1 -1
  4. package/templates/base/package.json +2 -1
  5. package/templates/base/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx +1 -1
  6. package/templates/base/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx +1 -1
  7. package/templates/base/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx +31 -12
  8. package/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx +16 -16
  9. package/templates/base/packages/nextjs/app/blockexplorer/address/[address]/page.tsx +1 -1
  10. package/templates/base/packages/nextjs/app/blockexplorer/page.tsx +1 -1
  11. package/templates/base/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx +1 -1
  12. package/templates/base/packages/nextjs/app/debug/_components/contract/ContractUI.tsx +33 -83
  13. package/templates/base/packages/nextjs/components/Footer.tsx +1 -1
  14. package/templates/base/packages/nextjs/components/Header.tsx +1 -1
  15. package/templates/base/packages/nextjs/components/ThemeProvider.tsx +10 -0
  16. package/templates/base/packages/nextjs/components/scaffold-eth/Address/AddressLinkWrapper.tsx +1 -1
  17. package/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx +1 -1
  18. package/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx +1 -1
  19. package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx +1 -1
  20. package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/BurnerWalletModal.tsx +1 -1
  21. package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx +1 -1
  22. package/templates/base/packages/nextjs/hooks/scaffold-eth/useFetchBlocks.ts +2 -2
  23. package/templates/base/packages/nextjs/scaffold.config.ts +4 -4
  24. package/templates/base/packages/nextjs/services/web3/wagmiConfig.tsx +1 -1
  25. package/templates/base/packages/nextjs/services/web3/wagmiConnectors.tsx +18 -12
  26. package/templates/base/packages/nextjs/styles/globals.css +9 -0
  27. package/templates/base/packages/nextjs/tailwind.config.js +4 -0
  28. package/templates/base/packages/nextjs/utils/scaffold-eth/decodeTxData.ts +1 -1
  29. package/templates/base/packages/nextjs/utils/scaffold-stylus/burner.ts +1 -1
  30. package/templates/base/packages/nextjs/utils/scaffold-stylus/index.ts +1 -1
  31. package/templates/base/packages/nextjs/utils/scaffold-stylus/networks.ts +1 -1
  32. package/templates/base/packages/nextjs/utils/scaffold-stylus/{chain.ts → supportedChains.ts} +5 -2
  33. package/templates/base/packages/stylus/.env.example +5 -0
  34. package/templates/base/packages/stylus/package.json +1 -1
  35. package/templates/base/packages/stylus/scripts/utils/command.ts +7 -7
  36. package/templates/base/packages/stylus/scripts/utils/deployment.ts +1 -1
  37. package/templates/base/packages/stylus/scripts/utils/network.ts +17 -2
  38. package/templates/base/packages/stylus/scripts/utils/type.ts +1 -1
  39. package/templates/base/packages/stylus/your-contract/src/lib.rs +21 -23
  40. package/templates/base/readme.md +18 -6
  41. package/templates/base/yarn.lock +590 -547
  42. package/templates/base/packages/stylus/your-contract/examples/counter.rs +0 -78
  43. package/templates/base/packages/stylus/your-contract/licenses/Apache-2.0 +0 -201
  44. package/templates/base/packages/stylus/your-contract/licenses/COPYRIGHT.md +0 -5
  45. package/templates/base/packages/stylus/your-contract/licenses/DCO.txt +0 -34
  46. package/templates/base/packages/stylus/your-contract/licenses/MIT +0 -21
package/dist/cli.js ADDED
@@ -0,0 +1,658 @@
1
+ import { execa } from 'execa';
2
+ import fs, { promises, lstatSync, readdirSync, existsSync } from 'fs';
3
+ import path from 'path';
4
+ import mergeJsonStr from 'merge-packages';
5
+ import url, { fileURLToPath } from 'url';
6
+ import ncp from 'ncp';
7
+ import { promisify } from 'util';
8
+ import { projectInstall } from 'pkg-install';
9
+ import chalk from 'chalk';
10
+ import Listr from 'listr';
11
+ import arg from 'arg';
12
+ import inquirer from 'inquirer';
13
+
14
+ const baseDir = "base";
15
+
16
+ const findFilesRecursiveSync = (baseDir, criteriaFn = () => true) => {
17
+ const subPaths = fs.readdirSync(baseDir);
18
+ const files = subPaths.map((relativePath) => {
19
+ const fullPath = path.resolve(baseDir, relativePath);
20
+ return fs.lstatSync(fullPath).isDirectory()
21
+ ? [...findFilesRecursiveSync(fullPath, criteriaFn)]
22
+ : criteriaFn(fullPath)
23
+ ? [fullPath]
24
+ : [];
25
+ });
26
+ return files.flat();
27
+ };
28
+
29
+ // @ts-expect-error We don't have types for this probably add .d.ts file
30
+ function mergePackageJson$1(targetPackageJsonPath, secondPackageJsonPath, isDev) {
31
+ const existsTarget = fs.existsSync(targetPackageJsonPath);
32
+ const existsSecond = fs.existsSync(secondPackageJsonPath);
33
+ if (!existsTarget && !existsSecond) {
34
+ return;
35
+ }
36
+ const targetPackageJson = existsTarget ? fs.readFileSync(targetPackageJsonPath, "utf8") : '{}';
37
+ const secondPackageJson = existsSecond ? fs.readFileSync(secondPackageJsonPath, "utf8") : '{}';
38
+ const mergedPkgStr = mergeJsonStr.default(targetPackageJson, secondPackageJson);
39
+ // Parse and reformat the merged JSON to ensure proper formatting
40
+ const mergedPkgObj = JSON.parse(mergedPkgStr);
41
+ const formattedPkgStr = JSON.stringify(mergedPkgObj, null, 2) + '\n';
42
+ fs.writeFileSync(targetPackageJsonPath, formattedPkgStr, "utf8");
43
+ if (isDev) {
44
+ const devStr = `TODO: write relevant information for the contributor`;
45
+ fs.writeFileSync(`${targetPackageJsonPath}.dev`, devStr, "utf8");
46
+ }
47
+ }
48
+
49
+ const { mkdir, link } = promises;
50
+ /**
51
+ * The goal is that this function has the same API as ncp, so they can be used
52
+ * interchangeably.
53
+ *
54
+ * - clobber not implemented
55
+ */
56
+ const linkRecursive = async (source, destination, options) => {
57
+ const passesFilter = options?.filter === undefined
58
+ ? true // no filter
59
+ : typeof options.filter === 'function'
60
+ ? options.filter(source) // filter is function
61
+ : options.filter.test(source); // filter is regex
62
+ if (!passesFilter) {
63
+ return;
64
+ }
65
+ if (lstatSync(source).isDirectory()) {
66
+ const subPaths = readdirSync(source);
67
+ await Promise.all(subPaths.map(async (subPath) => {
68
+ const sourceSubpath = path.join(source, subPath);
69
+ const isSubPathAFolder = lstatSync(sourceSubpath).isDirectory();
70
+ const destSubPath = path.join(destination, subPath);
71
+ const existsDestSubPath = existsSync(destSubPath);
72
+ if (isSubPathAFolder && !existsDestSubPath) {
73
+ await mkdir(destSubPath);
74
+ }
75
+ await linkRecursive(sourceSubpath, destSubPath, options);
76
+ }));
77
+ return;
78
+ }
79
+ return link(source, destination);
80
+ };
81
+
82
+ var extensions = [
83
+ {
84
+ extensionFlagValue: "erc-20",
85
+ description: "Adds support for ERC-20 token contracts, including balance checks and token transfers.",
86
+ repository: "https://github.com/Arb-Stylus/create-stylus-extensions",
87
+ branch: "erc-20"
88
+ },
89
+ {
90
+ extensionFlagValue: "erc-721",
91
+ description: "Adds support for ERC-721 NFT contracts, including supply, balance, listing, and transfer features.",
92
+ repository: "https://github.com/Arb-Stylus/create-stylus-extensions",
93
+ branch: "erc-721"
94
+ }
95
+ ];
96
+
97
+ function loadExtensions() {
98
+ return extensions;
99
+ }
100
+ function findExtensionByFlag(flagValue) {
101
+ return extensions.find((ext) => ext.extensionFlagValue === flagValue);
102
+ }
103
+
104
+ async function copyExtensionFile(extensionName, targetDirectory) {
105
+ if (!extensionName || extensionName.trim() === "") {
106
+ return;
107
+ }
108
+ const extensionConfig = findExtensionByFlag(extensionName);
109
+ if (!extensionConfig) {
110
+ return;
111
+ }
112
+ const targetPackagesDir = path.join(targetDirectory, "packages");
113
+ if (!fs.existsSync(targetPackagesDir)) {
114
+ return;
115
+ }
116
+ try {
117
+ const tempDir = path.join(targetDirectory, ".temp-extension");
118
+ fs.mkdirSync(tempDir, { recursive: true });
119
+ await cloneExtensionRepository(extensionConfig, tempDir);
120
+ await copyExtensionFiles(tempDir, targetPackagesDir, extensionConfig.extensionFlagValue);
121
+ await mergePackageJsonFiles(tempDir, targetPackagesDir, extensionConfig.extensionFlagValue);
122
+ await mergeRootPackageJson(tempDir, targetDirectory, extensionConfig.extensionFlagValue);
123
+ fs.rmSync(tempDir, { recursive: true, force: true });
124
+ }
125
+ catch (error) {
126
+ throw error;
127
+ }
128
+ }
129
+ async function cloneExtensionRepository(extension, tempDir) {
130
+ try {
131
+ const { stdout, stderr } = await execa("git", [
132
+ "clone",
133
+ "--branch", extension.branch,
134
+ "--single-branch",
135
+ "--depth", "1",
136
+ extension.repository,
137
+ tempDir
138
+ ]);
139
+ }
140
+ catch (error) {
141
+ throw error;
142
+ }
143
+ }
144
+ async function copyExtensionFiles(tempDir, targetPackagesDir, extensionName) {
145
+ let extensionPackageDir = path.join(tempDir, "packages", extensionName);
146
+ if (!fs.existsSync(extensionPackageDir)) {
147
+ extensionPackageDir = path.join(tempDir, "extensions", "packages", extensionName);
148
+ }
149
+ if (fs.existsSync(extensionPackageDir)) {
150
+ const targetExtensionDir = path.join(targetPackagesDir, extensionName);
151
+ copyDirectoryRecursive(extensionPackageDir, targetExtensionDir);
152
+ }
153
+ let extensionPackagesDir = path.join(tempDir, "packages");
154
+ if (!fs.existsSync(extensionPackagesDir)) {
155
+ extensionPackagesDir = path.join(tempDir, "extensions", "packages");
156
+ }
157
+ if (fs.existsSync(extensionPackagesDir)) {
158
+ const packages = fs.readdirSync(extensionPackagesDir);
159
+ for (const packageName of packages) {
160
+ if (packageName === extensionName) {
161
+ continue;
162
+ }
163
+ const sourcePackageDir = path.join(extensionPackagesDir, packageName);
164
+ const targetPackageDir = path.join(targetPackagesDir, packageName);
165
+ if (fs.existsSync(targetPackageDir)) {
166
+ mergeDirectoryRecursive(sourcePackageDir, targetPackageDir);
167
+ }
168
+ else {
169
+ copyDirectoryRecursive(sourcePackageDir, targetPackageDir);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ async function mergePackageJsonFiles(tempDir, targetPackagesDir, extensionName) {
175
+ let extensionPackagesDir = path.join(tempDir, "packages");
176
+ if (!fs.existsSync(extensionPackagesDir)) {
177
+ extensionPackagesDir = path.join(tempDir, "extensions", "packages");
178
+ }
179
+ if (!fs.existsSync(extensionPackagesDir)) {
180
+ return;
181
+ }
182
+ const packages = fs.readdirSync(extensionPackagesDir);
183
+ for (const packageName of packages) {
184
+ const sourcePackageDir = path.join(extensionPackagesDir, packageName);
185
+ const targetPackageDir = path.join(targetPackagesDir, packageName);
186
+ if (fs.existsSync(targetPackageDir)) {
187
+ const sourcePackageJsonPath = path.join(sourcePackageDir, "package.json");
188
+ const targetPackageJsonPath = path.join(targetPackageDir, "package.json");
189
+ if (fs.existsSync(sourcePackageJsonPath) && fs.existsSync(targetPackageJsonPath)) {
190
+ mergePackageJson(sourcePackageJsonPath, targetPackageJsonPath);
191
+ }
192
+ }
193
+ }
194
+ }
195
+ async function mergeRootPackageJson(tempDir, targetDirectory, extensionName) {
196
+ let extensionRootPackageJsonPath = path.join(tempDir, "package.json");
197
+ if (!fs.existsSync(extensionRootPackageJsonPath)) {
198
+ extensionRootPackageJsonPath = path.join(tempDir, "extensions", "package.json");
199
+ }
200
+ if (!fs.existsSync(extensionRootPackageJsonPath)) {
201
+ return;
202
+ }
203
+ const targetRootPackageJsonPath = path.join(targetDirectory, "package.json");
204
+ if (fs.existsSync(targetRootPackageJsonPath)) {
205
+ mergePackageJson(extensionRootPackageJsonPath, targetRootPackageJsonPath);
206
+ addPackageToWorkspaces(targetRootPackageJsonPath, extensionName);
207
+ }
208
+ }
209
+ function mergePackageJson(sourcePath, targetPath) {
210
+ try {
211
+ const sourcePackageJson = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
212
+ const targetPackageJson = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
213
+ if (sourcePackageJson.scripts && targetPackageJson.scripts) {
214
+ targetPackageJson.scripts = { ...targetPackageJson.scripts, ...sourcePackageJson.scripts };
215
+ }
216
+ if (sourcePackageJson.dependencies && targetPackageJson.dependencies) {
217
+ targetPackageJson.dependencies = { ...targetPackageJson.dependencies, ...sourcePackageJson.dependencies };
218
+ }
219
+ if (sourcePackageJson.devDependencies && targetPackageJson.devDependencies) {
220
+ targetPackageJson.devDependencies = { ...targetPackageJson.devDependencies, ...sourcePackageJson.devDependencies };
221
+ }
222
+ if (sourcePackageJson.peerDependencies && targetPackageJson.peerDependencies) {
223
+ targetPackageJson.peerDependencies = { ...targetPackageJson.peerDependencies, ...sourcePackageJson.peerDependencies };
224
+ }
225
+ fs.writeFileSync(targetPath, JSON.stringify(targetPackageJson, null, 2));
226
+ }
227
+ catch (error) {
228
+ // Error handling without logging
229
+ }
230
+ }
231
+ function addPackageToWorkspaces(packageJsonPath, packageName) {
232
+ try {
233
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
234
+ if (packageJson.workspaces && packageJson.workspaces.packages) {
235
+ const workspacePath = `packages/${packageName}`;
236
+ if (!packageJson.workspaces.packages.includes(workspacePath)) {
237
+ packageJson.workspaces.packages.push(workspacePath);
238
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
239
+ }
240
+ }
241
+ }
242
+ catch (error) {
243
+ // Error handling without logging
244
+ }
245
+ }
246
+ function copyDirectoryRecursive(source, destination) {
247
+ fs.mkdirSync(destination, { recursive: true });
248
+ const files = fs.readdirSync(source);
249
+ for (const file of files) {
250
+ const sourcePath = path.join(source, file);
251
+ const destPath = path.join(destination, file);
252
+ const stat = fs.statSync(sourcePath);
253
+ if (stat.isDirectory()) {
254
+ copyDirectoryRecursive(sourcePath, destPath);
255
+ }
256
+ else {
257
+ fs.copyFileSync(sourcePath, destPath);
258
+ }
259
+ }
260
+ }
261
+ function mergeDirectoryRecursive(source, destination) {
262
+ if (!fs.existsSync(destination)) {
263
+ fs.mkdirSync(destination, { recursive: true });
264
+ }
265
+ const files = fs.readdirSync(source);
266
+ for (const file of files) {
267
+ const sourcePath = path.join(source, file);
268
+ const destPath = path.join(destination, file);
269
+ const stat = fs.statSync(sourcePath);
270
+ if (stat.isDirectory()) {
271
+ mergeDirectoryRecursive(sourcePath, destPath);
272
+ }
273
+ else {
274
+ fs.copyFileSync(sourcePath, destPath);
275
+ }
276
+ }
277
+ }
278
+
279
+ const copy = promisify(ncp);
280
+ let copyOrLink = copy;
281
+ const isTemplateRegex = /([^\/\\]*?)\.template\./;
282
+ const isPackageJsonRegex = /package\.json/;
283
+ const isYarnLockRegex = /yarn\.lock/;
284
+ const isNextGeneratedRegex = /packages\/nextjs\/generated/;
285
+ const isArgsRegex = /([^\/\\]*?)\.args\./;
286
+ const isGitKeepRegex = /\.gitkeep/;
287
+ // Additional files/directories to exclude from template copying
288
+ const excludePatterns = [
289
+ /\.github\//, // GitHub specific files todo: add workflows/main.yml later
290
+ /CHANGELOG\.md/, // Changelog file
291
+ /__test.*__/, // All test directories (__test__, __tests__, etc.)
292
+ ];
293
+ const copyBaseFiles = async ({ dev: isDev }, basePath, targetDir) => {
294
+ await copyOrLink(basePath, targetDir, {
295
+ clobber: false,
296
+ filter: (fileName) => {
297
+ const isPackageJson = isPackageJsonRegex.test(fileName);
298
+ const isYarnLock = isYarnLockRegex.test(fileName);
299
+ const isNextGenerated = isNextGeneratedRegex.test(fileName);
300
+ const isGitKeep = isGitKeepRegex.test(fileName);
301
+ // Check if file matches any exclude pattern
302
+ const isExcluded = excludePatterns.some(pattern => pattern.test(fileName));
303
+ const skipAlways = isPackageJson || isGitKeep || isExcluded;
304
+ const skipDevOnly = isYarnLock || isNextGenerated;
305
+ const shouldSkip = skipAlways || (isDev && skipDevOnly);
306
+ return !shouldSkip;
307
+ },
308
+ });
309
+ ["stylus", "nextjs"].forEach(packageName => {
310
+ const envExamplePath = path.join(basePath, "packages", packageName, ".env.example");
311
+ const envPath = path.join(targetDir, "packages", packageName, ".env");
312
+ if (fs.existsSync(envExamplePath)) {
313
+ copy(envExamplePath, envPath);
314
+ }
315
+ });
316
+ const basePackageJsonPaths = findFilesRecursiveSync(basePath, (path) => isPackageJsonRegex.test(path));
317
+ basePackageJsonPaths.forEach((packageJsonPath) => {
318
+ const partialPath = packageJsonPath.split(basePath)[1];
319
+ mergePackageJson$1(path.join(targetDir, partialPath), path.join(basePath, partialPath), isDev);
320
+ });
321
+ if (isDev) {
322
+ const baseYarnLockPaths = findFilesRecursiveSync(basePath, (path) => isYarnLockRegex.test(path));
323
+ baseYarnLockPaths.forEach((yarnLockPath) => {
324
+ const partialPath = yarnLockPath.split(basePath)[1];
325
+ copy(path.join(basePath, partialPath), path.join(targetDir, partialPath));
326
+ });
327
+ const nextGeneratedPaths = findFilesRecursiveSync(basePath, (path) => isNextGeneratedRegex.test(path));
328
+ nextGeneratedPaths.forEach((nextGeneratedPath) => {
329
+ const partialPath = nextGeneratedPath.split(basePath)[1];
330
+ copy(path.join(basePath, partialPath), path.join(targetDir, partialPath));
331
+ });
332
+ }
333
+ };
334
+ const processTemplatedFiles = async ({ dev: isDev, extension }, basePath, targetDir) => {
335
+ const baseTemplatedFileDescriptors = findFilesRecursiveSync(basePath, (path) => isTemplateRegex.test(path)).map((baseTemplatePath) => ({
336
+ path: baseTemplatePath,
337
+ fileUrl: url.pathToFileURL(baseTemplatePath).href,
338
+ relativePath: baseTemplatePath.split(basePath)[1],
339
+ source: "base",
340
+ }));
341
+ await Promise.all(baseTemplatedFileDescriptors.map(async (templateFileDescriptor) => {
342
+ const templateTargetName = templateFileDescriptor.path.match(isTemplateRegex)?.[1];
343
+ const argsPath = templateFileDescriptor.relativePath.replace(isTemplateRegex, `${templateTargetName}.args.`);
344
+ // Load the template
345
+ const template = (await import(templateFileDescriptor.fileUrl)).default;
346
+ if (!template) {
347
+ throw new Error(`Template ${templateTargetName} from ${templateFileDescriptor.source} doesn't have a default export`);
348
+ }
349
+ if (typeof template !== "function") {
350
+ throw new Error(`Template ${templateTargetName} from ${templateFileDescriptor.source} is not exporting a function by default`);
351
+ }
352
+ // Collect args from multiple sources
353
+ const argsFileUrls = [];
354
+ // Check for args in target directory (from extensions)
355
+ if (extension) {
356
+ const extensionArgsPath = path.join(targetDir, argsPath);
357
+ if (fs.existsSync(extensionArgsPath)) {
358
+ argsFileUrls.push(url.pathToFileURL(extensionArgsPath).href);
359
+ }
360
+ }
361
+ // Load and combine all args
362
+ const argsModules = await Promise.all(argsFileUrls.map(async (argsFileUrl) => {
363
+ try {
364
+ return await import(argsFileUrl);
365
+ }
366
+ catch (error) {
367
+ console.warn(`Failed to load args from: ${argsFileUrl}`);
368
+ return {};
369
+ }
370
+ }));
371
+ // Combine all args into a single object
372
+ const combinedArgs = argsModules.reduce((acc, module) => {
373
+ return { ...acc, ...module };
374
+ }, {});
375
+ // Execute template with combined args
376
+ const output = template(combinedArgs);
377
+ const targetPath = path.join(targetDir, templateFileDescriptor.relativePath.split(templateTargetName)[0], templateTargetName);
378
+ fs.writeFileSync(targetPath, output);
379
+ if (isDev) {
380
+ const devOutput = `--- TEMPLATE FILE
381
+ templates/${templateFileDescriptor.source}${templateFileDescriptor.relativePath}
382
+
383
+
384
+ --- ARGS FILES
385
+ ${argsFileUrls.length > 0
386
+ ? argsFileUrls.map(url => `\t- ${url.split("packages")[1] || url}`).join("\n")
387
+ : "(no args files writing to the template)"}
388
+
389
+
390
+ --- RESULTING ARGS
391
+ ${Object.keys(combinedArgs).length > 0
392
+ ? Object.entries(combinedArgs)
393
+ .map(([key, value]) => `\t- ${key}:\t${JSON.stringify(value)}`)
394
+ .join("\n")
395
+ : "(no args sent for the template)"}
396
+ `;
397
+ fs.writeFileSync(`${targetPath}.dev`, devOutput);
398
+ }
399
+ }));
400
+ };
401
+ async function copyTemplateFiles(options, templateDir, targetDir) {
402
+ copyOrLink = options.dev ? linkRecursive : copy;
403
+ const basePath = path.join(templateDir, baseDir);
404
+ // 1. Copy base template to target directory
405
+ await copyBaseFiles(options, basePath, targetDir);
406
+ // 2. Copy extension files if extension is provided
407
+ if (options.extension) {
408
+ await copyExtensionFile(options.extension, targetDir);
409
+ }
410
+ // 3. Process templated files with extension args
411
+ await processTemplatedFiles(options, basePath, targetDir);
412
+ // 4. Clean up template and args files
413
+ await cleanupTemplateFiles(targetDir);
414
+ // 5. Initialize git repo to avoid husky error
415
+ await execa("git", ["init"], { cwd: targetDir });
416
+ await execa("git", ["checkout", "-b", "main"], { cwd: targetDir });
417
+ }
418
+ async function cleanupTemplateFiles(targetDir) {
419
+ const basePath = targetDir;
420
+ // Find all template and args files
421
+ const templateFiles = findFilesRecursiveSync(basePath, (path) => isTemplateRegex.test(path));
422
+ const argsFiles = findFilesRecursiveSync(basePath, (path) => isArgsRegex.test(path));
423
+ // Delete template files
424
+ templateFiles.forEach((templatePath) => {
425
+ try {
426
+ fs.unlinkSync(templatePath);
427
+ }
428
+ catch (error) {
429
+ console.warn(`Failed to delete template file: ${templatePath}`);
430
+ }
431
+ });
432
+ // Delete args files
433
+ argsFiles.forEach((argsPath) => {
434
+ try {
435
+ fs.unlinkSync(argsPath);
436
+ }
437
+ catch (error) {
438
+ console.warn(`Failed to delete args file: ${argsPath}`);
439
+ }
440
+ });
441
+ }
442
+
443
+ async function createProjectDirectory(projectName) {
444
+ // Check if directory already exists
445
+ if (fs.existsSync(projectName)) {
446
+ // If directory exists, check if it's empty
447
+ if (fs.readdirSync(projectName).length > 0) {
448
+ throw new Error(`Directory ${projectName} already exists and is not empty. Cannot continue.`);
449
+ }
450
+ // If directory exists and is empty, we can proceed (or do nothing here, as it's already created and empty)
451
+ // For clarity, we can return true or simply let the function continue if no further action is needed.
452
+ // In this case, since the directory exists and is empty, the goal is achieved.
453
+ return true;
454
+ }
455
+ try {
456
+ const result = await execa("mkdir", [projectName]);
457
+ if (result.failed) {
458
+ throw new Error("There was a problem running the mkdir command");
459
+ }
460
+ }
461
+ catch (error) {
462
+ throw new Error(`Failed to create directory: ${error instanceof Error ? error.message : String(error)}`);
463
+ }
464
+ return true;
465
+ }
466
+
467
+ async function installPackages(targetDir, options) {
468
+ // Condition to check if 'devnet' is included and only update submodules
469
+ // if (options.extensions?.includes("starknet-native")) {
470
+ // }
471
+ return projectInstall({
472
+ cwd: targetDir,
473
+ prefer: "yarn",
474
+ });
475
+ }
476
+
477
+ async function createFirstGitCommit(targetDir) {
478
+ try {
479
+ await execa("git", ["add", "-A"], { cwd: targetDir });
480
+ await execa("git", ["commit", "-m", "Initial commit with 🏗️ Scaffold-Stylus", "--no-verify"], { cwd: targetDir });
481
+ }
482
+ catch (e) {
483
+ throw new Error("Failed to initialize git repository", {
484
+ cause: e?.stderr ?? e,
485
+ });
486
+ }
487
+ }
488
+
489
+ async function prettierFormat(targetDir) {
490
+ try {
491
+ const nextjsPath = path.join(targetDir, "packages/nextjs");
492
+ const result = await execa("yarn", ["format"], { cwd: nextjsPath });
493
+ if (result.failed) {
494
+ throw new Error("There was a problem running the format command");
495
+ }
496
+ }
497
+ catch (error) {
498
+ throw new Error("Failed to format Next.js project", { cause: error });
499
+ }
500
+ return true;
501
+ }
502
+
503
+ async function renderOutroMessage(options) {
504
+ let message = `
505
+ \n
506
+ ${chalk.bold.green("Congratulations!")} Your project has been scaffolded! 🎉
507
+
508
+ ${chalk.bold("Next steps:")}
509
+
510
+ ${chalk.dim("cd")} ${options.directory}
511
+ `;
512
+ message += `
513
+ \t${chalk.bold("Start the local development node")}
514
+ \t${chalk.dim("yarn")} chain
515
+ `;
516
+ message += `
517
+ \t${chalk.bold("In a new terminal window, deploy your contracts")}
518
+ \t${chalk.dim("yarn")} deploy
519
+ `;
520
+ message += `
521
+ \t${chalk.bold("In a new terminal window, start the frontend")}
522
+ \t${chalk.dim("yarn")} start
523
+ `;
524
+ message += `
525
+ ${chalk.bold.green("Thanks for using Scaffold-Stylus 🙏, Happy Building!")}
526
+ `;
527
+ console.log(message);
528
+ }
529
+
530
+ async function createProject(options) {
531
+ console.log(`\n`);
532
+ const currentFileUrl = import.meta.url;
533
+ const templateDirectory = path.resolve(decodeURI(fileURLToPath(currentFileUrl)), "../../templates");
534
+ const targetDirectory = path.resolve(process.cwd(), options.directory);
535
+ const tasks = new Listr([
536
+ {
537
+ title: `📁 Create project directory ${targetDirectory}`,
538
+ task: () => createProjectDirectory(options.directory),
539
+ },
540
+ {
541
+ title: `🚀 Creating a new Scaffold-Stylus 2 app${options.extension
542
+ ? ` with ${chalk.green.bold(options.extension)} extension`
543
+ : ""} in ${chalk.green.bold(options.directory)}`,
544
+ task: () => copyTemplateFiles(options, templateDirectory, targetDirectory),
545
+ },
546
+ {
547
+ title: `📦 Installing dependencies with yarn, this could take a while`,
548
+ task: () => installPackages(targetDirectory),
549
+ skip: () => {
550
+ if (!options.install) {
551
+ return "Manually skipped";
552
+ }
553
+ },
554
+ },
555
+ {
556
+ title: "🪄 Formatting Next.js files with prettier",
557
+ task: () => prettierFormat(targetDirectory),
558
+ skip: () => {
559
+ if (!options.install) {
560
+ return "Skipping because prettier install was skipped";
561
+ }
562
+ },
563
+ },
564
+ {
565
+ title: `📡 Initializing Git repository`,
566
+ task: () => createFirstGitCommit(targetDirectory),
567
+ },
568
+ ]);
569
+ try {
570
+ await tasks.run();
571
+ renderOutroMessage(options);
572
+ }
573
+ catch (error) {
574
+ console.log("%s Error occurred", chalk.red.bold("ERROR"), error);
575
+ console.log("%s Exiting...", chalk.red.bold("Uh oh! 😕 Sorry about that!"));
576
+ }
577
+ }
578
+
579
+ // TODO update smartContractFramework code with general extensions
580
+ function parseArgumentsIntoOptions(rawArgs) {
581
+ const args = arg({
582
+ "--skip-install": Boolean,
583
+ "-s": "--skip-install",
584
+ "--dev": Boolean,
585
+ "--dir": String,
586
+ "-d": "--dir",
587
+ "--extension": String,
588
+ "-e": "--extension",
589
+ }, {
590
+ argv: rawArgs.slice(2).map((a) => a.toLowerCase()),
591
+ });
592
+ const skipInstall = args["--skip-install"] ?? null;
593
+ const dev = args["--dev"] ?? false; // info: use false avoid asking user
594
+ const directory = args["--dir"] ?? null;
595
+ const extension = args["--extension"] ?? null;
596
+ return {
597
+ directory,
598
+ install: skipInstall ? false : null,
599
+ dev,
600
+ extension,
601
+ };
602
+ }
603
+
604
+ // default values for unspecified args
605
+ const defaultOptions = {
606
+ directory: "./my-dapp-example",
607
+ install: true,
608
+ dev: false};
609
+ async function promptForMissingOptions(options) {
610
+ const cliAnswers = Object.fromEntries(Object.entries(options).filter(([key, value]) => value !== null));
611
+ const extensions = loadExtensions();
612
+ extensions.map(ext => ({
613
+ name: `${ext.extensionFlagValue} - ${ext.description}`,
614
+ value: ext.extensionFlagValue,
615
+ }));
616
+ const questions = [
617
+ {
618
+ type: "input",
619
+ name: "directory",
620
+ message: "Where do you want to install the new files? Choose ./ (root folder) or provide a new folder name.",
621
+ default: defaultOptions.directory,
622
+ validate: (value) => value.length > 0,
623
+ },
624
+ {
625
+ type: "confirm",
626
+ name: "install",
627
+ message: "Install dependencies?",
628
+ default: defaultOptions.install,
629
+ },
630
+ ];
631
+ const answers = await inquirer.prompt(questions, cliAnswers);
632
+ const mergedOptions = {
633
+ directory: options.directory ?? answers.directory,
634
+ install: options.install ?? answers.install,
635
+ dev: options.dev ?? defaultOptions.dev,
636
+ extension: options.extension ?? answers.extension,
637
+ };
638
+ return mergedOptions;
639
+ }
640
+
641
+ const TITLE_TEXT = `
642
+ ${chalk.bold.blue("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+")}
643
+ ${chalk.bold.blue("| Create Scaffold-Stylus app |")}
644
+ ${chalk.bold.blue("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+")}
645
+ `;
646
+ function renderIntroMessage() {
647
+ console.log(TITLE_TEXT);
648
+ }
649
+
650
+ async function cli(args) {
651
+ renderIntroMessage();
652
+ const rawOptions = parseArgumentsIntoOptions(args);
653
+ const options = await promptForMissingOptions(rawOptions);
654
+ await createProject(options);
655
+ }
656
+
657
+ export { cli };
658
+ //# sourceMappingURL=cli.js.map