@blu1606/create-walrus-app 0.1.0

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 (119) hide show
  1. package/dist/__tests__/helpers/adapter-compliance.d.ts +2 -0
  2. package/dist/__tests__/helpers/adapter-compliance.js +47 -0
  3. package/dist/__tests__/helpers/fixtures.d.ts +21 -0
  4. package/dist/__tests__/helpers/fixtures.js +30 -0
  5. package/dist/__tests__/helpers/fs-helpers.d.ts +12 -0
  6. package/dist/__tests__/helpers/fs-helpers.js +35 -0
  7. package/dist/__tests__/helpers/index.d.ts +4 -0
  8. package/dist/__tests__/helpers/index.js +4 -0
  9. package/dist/__tests__/helpers/test-hooks.d.ts +3 -0
  10. package/dist/__tests__/helpers/test-hooks.js +18 -0
  11. package/dist/context.d.ts +2 -0
  12. package/dist/context.js +43 -0
  13. package/dist/context.test.d.ts +1 -0
  14. package/dist/context.test.js +98 -0
  15. package/dist/generator/file-ops.d.ts +12 -0
  16. package/dist/generator/file-ops.js +40 -0
  17. package/dist/generator/index.d.ts +2 -0
  18. package/dist/generator/index.js +75 -0
  19. package/dist/generator/index.test.d.ts +1 -0
  20. package/dist/generator/index.test.js +143 -0
  21. package/dist/generator/layers.d.ts +3 -0
  22. package/dist/generator/layers.js +59 -0
  23. package/dist/generator/layers.test.d.ts +1 -0
  24. package/dist/generator/layers.test.js +92 -0
  25. package/dist/generator/merge.d.ts +14 -0
  26. package/dist/generator/merge.js +62 -0
  27. package/dist/generator/merge.test.d.ts +1 -0
  28. package/dist/generator/merge.test.js +79 -0
  29. package/dist/generator/transform.d.ts +21 -0
  30. package/dist/generator/transform.js +52 -0
  31. package/dist/generator/transform.test.d.ts +1 -0
  32. package/dist/generator/transform.test.js +51 -0
  33. package/dist/generator/types.d.ts +18 -0
  34. package/dist/generator/types.js +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +106 -0
  37. package/dist/matrix.d.ts +31 -0
  38. package/dist/matrix.js +31 -0
  39. package/dist/matrix.test.d.ts +1 -0
  40. package/dist/matrix.test.js +70 -0
  41. package/dist/post-install/git.d.ts +12 -0
  42. package/dist/post-install/git.js +94 -0
  43. package/dist/post-install/index.d.ts +16 -0
  44. package/dist/post-install/index.js +56 -0
  45. package/dist/post-install/messages.d.ts +9 -0
  46. package/dist/post-install/messages.js +49 -0
  47. package/dist/post-install/package-manager.d.ts +14 -0
  48. package/dist/post-install/package-manager.js +57 -0
  49. package/dist/post-install/validator.d.ts +14 -0
  50. package/dist/post-install/validator.js +114 -0
  51. package/dist/prompts.d.ts +2 -0
  52. package/dist/prompts.js +115 -0
  53. package/dist/test-base.d.ts +1 -0
  54. package/dist/test-base.js +42 -0
  55. package/dist/types.d.ts +19 -0
  56. package/dist/types.js +1 -0
  57. package/dist/types.test.d.ts +1 -0
  58. package/dist/types.test.js +65 -0
  59. package/dist/utils/detect-pm.d.ts +2 -0
  60. package/dist/utils/detect-pm.js +10 -0
  61. package/dist/utils/detect-pm.test.d.ts +1 -0
  62. package/dist/utils/detect-pm.test.js +52 -0
  63. package/dist/utils/logger.d.ts +6 -0
  64. package/dist/utils/logger.js +7 -0
  65. package/dist/validator.d.ts +3 -0
  66. package/dist/validator.js +48 -0
  67. package/dist/validator.test.d.ts +1 -0
  68. package/dist/validator.test.js +96 -0
  69. package/package.json +68 -0
  70. package/templates/base/.env.example +31 -0
  71. package/templates/base/README.md +54 -0
  72. package/templates/base/package.json +19 -0
  73. package/templates/base/src/adapters/storage.ts +58 -0
  74. package/templates/base/src/types/index.ts +9 -0
  75. package/templates/base/src/types/walrus.ts +22 -0
  76. package/templates/base/src/utils/env.ts +41 -0
  77. package/templates/base/src/utils/format.ts +29 -0
  78. package/templates/base/tsconfig.json +19 -0
  79. package/templates/gallery/README.md +44 -0
  80. package/templates/gallery/package.json +6 -0
  81. package/templates/gallery/src/App.tsx +21 -0
  82. package/templates/gallery/src/components/FileCard.tsx +27 -0
  83. package/templates/gallery/src/components/GalleryGrid.tsx +30 -0
  84. package/templates/gallery/src/components/UploadModal.tsx +45 -0
  85. package/templates/gallery/src/styles.css +58 -0
  86. package/templates/gallery/src/types/gallery.ts +13 -0
  87. package/templates/gallery/src/utils/index-manager.ts +37 -0
  88. package/templates/react/.eslintrc.json +26 -0
  89. package/templates/react/README.md +80 -0
  90. package/templates/react/index.html +13 -0
  91. package/templates/react/package.json +32 -0
  92. package/templates/react/src/App.tsx +14 -0
  93. package/templates/react/src/components/Layout.tsx +21 -0
  94. package/templates/react/src/components/WalletConnect.tsx +21 -0
  95. package/templates/react/src/dapp-kit.css +1 -0
  96. package/templates/react/src/hooks/useStorage.ts +40 -0
  97. package/templates/react/src/hooks/useWallet.ts +16 -0
  98. package/templates/react/src/index.css +50 -0
  99. package/templates/react/src/index.ts +10 -0
  100. package/templates/react/src/main.tsx +17 -0
  101. package/templates/react/src/providers/QueryProvider.tsx +18 -0
  102. package/templates/react/src/providers/WalletProvider.tsx +37 -0
  103. package/templates/react/tsconfig.json +27 -0
  104. package/templates/react/tsconfig.node.json +10 -0
  105. package/templates/react/vite.config.ts +19 -0
  106. package/templates/sdk-mysten/README.md +65 -0
  107. package/templates/sdk-mysten/package.json +14 -0
  108. package/templates/sdk-mysten/src/adapter.ts +80 -0
  109. package/templates/sdk-mysten/src/client.ts +45 -0
  110. package/templates/sdk-mysten/src/config.ts +33 -0
  111. package/templates/sdk-mysten/src/index.ts +11 -0
  112. package/templates/sdk-mysten/src/types.ts +19 -0
  113. package/templates/sdk-mysten/test/adapter.test.ts +20 -0
  114. package/templates/simple-upload/README.md +24 -0
  115. package/templates/simple-upload/package.json +6 -0
  116. package/templates/simple-upload/src/App.tsx +27 -0
  117. package/templates/simple-upload/src/components/FilePreview.tsx +40 -0
  118. package/templates/simple-upload/src/components/UploadForm.tsx +51 -0
  119. package/templates/simple-upload/src/styles.css +33 -0
@@ -0,0 +1,59 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ const __filename = fileURLToPath(import.meta.url);
4
+ const __dirname = path.dirname(__filename);
5
+ // Templates are in packages/cli/templates (published with package)
6
+ const TEMPLATE_ROOT = path.join(__dirname, '../../templates');
7
+ /**
8
+ * Validate that a layer path is within the template root (prevent path traversal)
9
+ */
10
+ function validateLayerPath(layerPath) {
11
+ const normalized = path.resolve(layerPath);
12
+ const root = path.resolve(TEMPLATE_ROOT);
13
+ if (!normalized.startsWith(root)) {
14
+ throw new Error(`Invalid layer path: ${layerPath} is outside template root`);
15
+ }
16
+ }
17
+ export function resolveLayers(context) {
18
+ const layers = [
19
+ {
20
+ name: 'base',
21
+ path: path.join(TEMPLATE_ROOT, 'base'),
22
+ priority: 1,
23
+ },
24
+ {
25
+ name: `sdk-${context.sdk}`,
26
+ path: path.join(TEMPLATE_ROOT, `sdk-${context.sdk}`),
27
+ priority: 2,
28
+ },
29
+ {
30
+ name: context.framework,
31
+ path: path.join(TEMPLATE_ROOT, context.framework),
32
+ priority: 3,
33
+ },
34
+ {
35
+ name: context.useCase,
36
+ path: path.join(TEMPLATE_ROOT, context.useCase),
37
+ priority: 4,
38
+ },
39
+ ];
40
+ // Optional: Tailwind layer
41
+ if (context.tailwind) {
42
+ layers.push({
43
+ name: 'tailwind',
44
+ path: path.join(TEMPLATE_ROOT, 'tailwind'),
45
+ priority: 5,
46
+ });
47
+ }
48
+ // Optional: Analytics layer
49
+ if (context.analytics) {
50
+ layers.push({
51
+ name: 'analytics',
52
+ path: path.join(TEMPLATE_ROOT, 'analytics'),
53
+ priority: 6,
54
+ });
55
+ }
56
+ // Validate all layer paths before returning
57
+ layers.forEach((layer) => validateLayerPath(layer.path));
58
+ return layers;
59
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,92 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { resolveLayers } from './layers.js';
3
+ import path from 'node:path';
4
+ describe('resolveLayers', () => {
5
+ it('should resolve base layers in correct priority order', () => {
6
+ const context = {
7
+ projectName: 'test-app',
8
+ projectPath: '/path/to/test-app',
9
+ sdk: 'mysten',
10
+ framework: 'react',
11
+ useCase: 'simple-upload',
12
+ analytics: false,
13
+ tailwind: false,
14
+ packageManager: 'pnpm',
15
+ };
16
+ const layers = resolveLayers(context);
17
+ expect(layers.length).toBe(4);
18
+ expect(layers[0].name).toBe('base');
19
+ expect(layers[0].priority).toBe(1);
20
+ expect(layers[1].name).toBe('sdk-mysten');
21
+ expect(layers[1].priority).toBe(2);
22
+ expect(layers[2].name).toBe('react');
23
+ expect(layers[2].priority).toBe(3);
24
+ expect(layers[3].name).toBe('simple-upload');
25
+ expect(layers[3].priority).toBe(4);
26
+ });
27
+ it('should include tailwind layer when enabled', () => {
28
+ const context = {
29
+ projectName: 'test-app',
30
+ projectPath: '/path/to/test-app',
31
+ sdk: 'mysten',
32
+ framework: 'react',
33
+ useCase: 'gallery',
34
+ analytics: false,
35
+ tailwind: true,
36
+ packageManager: 'pnpm',
37
+ };
38
+ const layers = resolveLayers(context);
39
+ expect(layers.length).toBe(5);
40
+ expect(layers[4].name).toBe('tailwind');
41
+ expect(layers[4].priority).toBe(5);
42
+ });
43
+ it('should include analytics layer when enabled', () => {
44
+ const context = {
45
+ projectName: 'test-app',
46
+ projectPath: '/path/to/test-app',
47
+ sdk: 'mysten',
48
+ framework: 'vue',
49
+ useCase: 'defi-nft',
50
+ analytics: true,
51
+ tailwind: false,
52
+ packageManager: 'npm',
53
+ };
54
+ const layers = resolveLayers(context);
55
+ expect(layers.length).toBe(5);
56
+ expect(layers[4].name).toBe('analytics');
57
+ expect(layers[4].priority).toBe(6);
58
+ });
59
+ it('should include both optional layers when enabled', () => {
60
+ const context = {
61
+ projectName: 'test-app',
62
+ projectPath: '/path/to/test-app',
63
+ sdk: 'mysten',
64
+ framework: 'plain-ts',
65
+ useCase: 'simple-upload',
66
+ analytics: true,
67
+ tailwind: true,
68
+ packageManager: 'yarn',
69
+ };
70
+ const layers = resolveLayers(context);
71
+ expect(layers.length).toBe(6);
72
+ expect(layers[4].name).toBe('tailwind');
73
+ expect(layers[5].name).toBe('analytics');
74
+ });
75
+ it('should use correct template paths', () => {
76
+ const context = {
77
+ projectName: 'test-app',
78
+ projectPath: '/path/to/test-app',
79
+ sdk: 'mysten',
80
+ framework: 'react',
81
+ useCase: 'simple-upload',
82
+ analytics: false,
83
+ tailwind: false,
84
+ packageManager: 'pnpm',
85
+ };
86
+ const layers = resolveLayers(context);
87
+ layers.forEach((layer) => {
88
+ expect(layer.path).toContain('templates');
89
+ expect(path.isAbsolute(layer.path)).toBe(true);
90
+ });
91
+ });
92
+ });
@@ -0,0 +1,14 @@
1
+ type JSONValue = string | number | boolean | null | JSONValue[] | {
2
+ [key: string]: JSONValue;
3
+ };
4
+ /**
5
+ * Deep merge two objects
6
+ * Arrays and primitives are replaced, objects are merged recursively
7
+ * Note: null values override (later layers win)
8
+ */
9
+ export declare function deepMerge(target: JSONValue, source: JSONValue): JSONValue;
10
+ /**
11
+ * Merge multiple package.json files from layers
12
+ */
13
+ export declare function mergePackageJsonFiles(layers: string[], outputPath: string): Promise<void>;
14
+ export {};
@@ -0,0 +1,62 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import sortPackageJson from 'sort-package-json';
4
+ /**
5
+ * Deep merge two objects
6
+ * Arrays and primitives are replaced, objects are merged recursively
7
+ * Note: null values override (later layers win)
8
+ */
9
+ export function deepMerge(target, source) {
10
+ // Handle undefined (skip)
11
+ if (source === undefined) {
12
+ return target;
13
+ }
14
+ // Handle null explicitly - null overrides
15
+ if (source === null) {
16
+ return null;
17
+ }
18
+ // Arrays: Replace entirely (don't merge)
19
+ if (Array.isArray(source)) {
20
+ return source;
21
+ }
22
+ // Objects: Merge recursively
23
+ if (typeof source === 'object' && typeof target === 'object') {
24
+ const result = { ...target };
25
+ for (const key in source) {
26
+ const sourceValue = source[key];
27
+ const targetValue = result[key];
28
+ if (targetValue &&
29
+ typeof targetValue === 'object' &&
30
+ !Array.isArray(targetValue) &&
31
+ sourceValue &&
32
+ typeof sourceValue === 'object' &&
33
+ !Array.isArray(sourceValue)) {
34
+ result[key] = deepMerge(targetValue, sourceValue);
35
+ }
36
+ else {
37
+ result[key] = sourceValue;
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ // Primitives: Replace
43
+ return source;
44
+ }
45
+ /**
46
+ * Merge multiple package.json files from layers
47
+ */
48
+ export async function mergePackageJsonFiles(layers, outputPath) {
49
+ let merged = {};
50
+ for (const layerPath of layers) {
51
+ const pkgPath = path.join(layerPath, 'package.json');
52
+ if (await fs.pathExists(pkgPath)) {
53
+ const pkgJson = await fs.readJson(pkgPath);
54
+ const result = deepMerge(merged, pkgJson);
55
+ // Ensure result is an object (package.json should always be an object)
56
+ merged = (result && typeof result === 'object' && !Array.isArray(result)) ? result : merged;
57
+ }
58
+ }
59
+ // Sort keys for consistency
60
+ const sorted = sortPackageJson(merged);
61
+ await fs.writeJson(outputPath, sorted, { spaces: 2 });
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { deepMerge } from './merge.js';
3
+ describe('deepMerge', () => {
4
+ it('should merge objects recursively', () => {
5
+ const target = { a: 1, b: { c: 2 } };
6
+ const source = { b: { d: 3 }, e: 4 };
7
+ const result = deepMerge(target, source);
8
+ expect(result).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 });
9
+ });
10
+ it('should replace arrays entirely', () => {
11
+ const target = { arr: [1, 2, 3] };
12
+ const source = { arr: [4, 5] };
13
+ const result = deepMerge(target, source);
14
+ expect(result).toEqual({ arr: [4, 5] });
15
+ });
16
+ it('should replace primitives', () => {
17
+ const target = { a: 1, b: 'old' };
18
+ const source = { a: 2, b: 'new' };
19
+ const result = deepMerge(target, source);
20
+ expect(result).toEqual({ a: 2, b: 'new' });
21
+ });
22
+ it('should handle null and undefined', () => {
23
+ // undefined: skip (return target)
24
+ const target1 = { a: 1 };
25
+ const source1 = undefined;
26
+ const result1 = deepMerge(target1, source1);
27
+ expect(result1).toEqual({ a: 1 });
28
+ // null: override (later layers win)
29
+ const target2 = { a: 1 };
30
+ const source2 = null;
31
+ const result2 = deepMerge(target2, source2);
32
+ expect(result2).toBeNull();
33
+ });
34
+ it('should merge nested objects deeply', () => {
35
+ const target = {
36
+ dependencies: { react: '^18.0.0' },
37
+ scripts: { build: 'tsc' },
38
+ };
39
+ const source = {
40
+ dependencies: { 'react-dom': '^18.0.0' },
41
+ scripts: { dev: 'vite' },
42
+ };
43
+ const result = deepMerge(target, source);
44
+ expect(result).toEqual({
45
+ dependencies: { react: '^18.0.0', 'react-dom': '^18.0.0' },
46
+ scripts: { build: 'tsc', dev: 'vite' },
47
+ });
48
+ });
49
+ it('should handle package.json-like merge', () => {
50
+ const base = {
51
+ name: 'base',
52
+ version: '1.0.0',
53
+ dependencies: {
54
+ commander: '^11.0.0',
55
+ },
56
+ };
57
+ const overlay = {
58
+ name: 'overlay',
59
+ dependencies: {
60
+ react: '^18.0.0',
61
+ },
62
+ devDependencies: {
63
+ typescript: '^5.0.0',
64
+ },
65
+ };
66
+ const result = deepMerge(base, overlay);
67
+ expect(result).toEqual({
68
+ name: 'overlay',
69
+ version: '1.0.0',
70
+ dependencies: {
71
+ commander: '^11.0.0',
72
+ react: '^18.0.0',
73
+ },
74
+ devDependencies: {
75
+ typescript: '^5.0.0',
76
+ },
77
+ });
78
+ });
79
+ });
@@ -0,0 +1,21 @@
1
+ import type { Context } from '../types.js';
2
+ interface TransformVariables {
3
+ projectName: string;
4
+ sdkName: string;
5
+ framework: string;
6
+ useCase: string;
7
+ }
8
+ /**
9
+ * Build transformation variables from context
10
+ */
11
+ export declare function buildVariables(context: Context): TransformVariables;
12
+ /**
13
+ * Transform string with variable substitution
14
+ */
15
+ export declare function transformString(content: string, vars: TransformVariables): string;
16
+ /**
17
+ * Transform all text files in directory
18
+ * Includes code files (.ts, .tsx, .js, .jsx) and config files (.md, .json, .html)
19
+ */
20
+ export declare function transformDirectory(dir: string, vars: TransformVariables, extensions?: string[]): Promise<void>;
21
+ export {};
@@ -0,0 +1,52 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ /**
4
+ * Build transformation variables from context
5
+ */
6
+ export function buildVariables(context) {
7
+ return {
8
+ projectName: context.projectName,
9
+ sdkName: context.sdk,
10
+ framework: context.framework,
11
+ useCase: context.useCase,
12
+ };
13
+ }
14
+ /**
15
+ * Transform string with variable substitution
16
+ */
17
+ export function transformString(content, vars) {
18
+ return content
19
+ .replace(/\{\{projectName\}\}/g, vars.projectName)
20
+ .replace(/\{\{sdkName\}\}/g, vars.sdkName)
21
+ .replace(/\{\{framework\}\}/g, vars.framework)
22
+ .replace(/\{\{useCase\}\}/g, vars.useCase);
23
+ }
24
+ /**
25
+ * Transform all text files in directory
26
+ * Includes code files (.ts, .tsx, .js, .jsx) and config files (.md, .json, .html)
27
+ */
28
+ export async function transformDirectory(dir, vars, extensions = [
29
+ '.md',
30
+ '.json',
31
+ '.html',
32
+ '.ts',
33
+ '.tsx',
34
+ '.js',
35
+ '.jsx',
36
+ '.css',
37
+ '.scss',
38
+ '.vue',
39
+ ]) {
40
+ const entries = await fs.readdir(dir, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ const fullPath = path.join(dir, entry.name);
43
+ if (entry.isDirectory()) {
44
+ await transformDirectory(fullPath, vars, extensions);
45
+ }
46
+ else if (extensions.some((ext) => entry.name.endsWith(ext))) {
47
+ const content = await fs.readFile(fullPath, 'utf-8');
48
+ const transformed = transformString(content, vars);
49
+ await fs.writeFile(fullPath, transformed, 'utf-8');
50
+ }
51
+ }
52
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { transformString, buildVariables } from './transform.js';
3
+ describe('transformString', () => {
4
+ const vars = {
5
+ projectName: 'my-walrus-app',
6
+ sdkName: 'mysten',
7
+ framework: 'react',
8
+ useCase: 'simple-upload',
9
+ };
10
+ it('should replace projectName placeholder', () => {
11
+ const input = '# {{projectName}}';
12
+ const result = transformString(input, vars);
13
+ expect(result).toBe('# my-walrus-app');
14
+ });
15
+ it('should replace multiple placeholders', () => {
16
+ const input = 'Project: {{projectName}}, SDK: {{sdkName}}, Framework: {{framework}}';
17
+ const result = transformString(input, vars);
18
+ expect(result).toBe('Project: my-walrus-app, SDK: mysten, Framework: react');
19
+ });
20
+ it('should handle multiple occurrences of same placeholder', () => {
21
+ const input = '{{projectName}} is a {{projectName}} project';
22
+ const result = transformString(input, vars);
23
+ expect(result).toBe('my-walrus-app is a my-walrus-app project');
24
+ });
25
+ it('should not modify text without placeholders', () => {
26
+ const input = 'This is plain text';
27
+ const result = transformString(input, vars);
28
+ expect(result).toBe('This is plain text');
29
+ });
30
+ });
31
+ describe('buildVariables', () => {
32
+ it('should extract variables from context', () => {
33
+ const context = {
34
+ projectName: 'test-app',
35
+ projectPath: '/path/to/test-app',
36
+ sdk: 'mysten',
37
+ framework: 'react',
38
+ useCase: 'gallery',
39
+ analytics: false,
40
+ tailwind: true,
41
+ packageManager: 'pnpm',
42
+ };
43
+ const vars = buildVariables(context);
44
+ expect(vars).toEqual({
45
+ projectName: 'test-app',
46
+ sdkName: 'mysten',
47
+ framework: 'react',
48
+ useCase: 'gallery',
49
+ });
50
+ });
51
+ });
@@ -0,0 +1,18 @@
1
+ import type { Context } from '../types.js';
2
+ export interface Layer {
3
+ name: string;
4
+ path: string;
5
+ priority: number;
6
+ }
7
+ export interface GeneratorOptions {
8
+ context: Context;
9
+ templateDir: string;
10
+ targetDir: string;
11
+ dryRun?: boolean;
12
+ }
13
+ export interface GeneratorResult {
14
+ success: boolean;
15
+ projectPath: string;
16
+ filesCreated: number;
17
+ error?: Error;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { runPrompts } from './prompts.js';
4
+ import { buildContext } from './context.js';
5
+ import { validateContext } from './validator.js';
6
+ import { logger } from './utils/logger.js';
7
+ import { generateProject } from './generator/index.js';
8
+ import { runPostInstall } from './post-install/index.js';
9
+ import { readFileSync } from 'node:fs';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { dirname, join } from 'node:path';
12
+ import fs from 'fs-extra';
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
16
+ // Track current generation path for cleanup on interrupt
17
+ let currentGenerationPath = null;
18
+ program
19
+ .name('create-walrus-app')
20
+ .description('Interactive CLI for scaffolding Walrus applications')
21
+ .version(packageJson.version)
22
+ .argument('[project-name]', 'Project directory name')
23
+ .option('--sdk <sdk>', 'SDK to use (mysten | tusky | hibernuts)')
24
+ .option('--framework <framework>', 'Framework (react | vue | plain-ts)')
25
+ .option('--use-case <use-case>', 'Use case (simple-upload | gallery | defi-nft)')
26
+ .option('--analytics', 'Include Blockberry analytics', false)
27
+ .option('--no-tailwind', 'Exclude Tailwind CSS')
28
+ .option('--skip-install', 'Skip dependency installation', false)
29
+ .option('--skip-git', 'Skip git initialization', false)
30
+ .option('--skip-validation', 'Skip project validation', false)
31
+ .option('-p, --package-manager <pm>', 'Package manager to use (npm | pnpm | yarn | bun)')
32
+ .action(async (projectNameArg, options) => {
33
+ try {
34
+ logger.info('๐Ÿš€ Welcome to Walrus Starter Kit!');
35
+ // Build initial context from args
36
+ const initialContext = {
37
+ projectName: projectNameArg,
38
+ ...options,
39
+ };
40
+ // Run interactive prompts (skips questions with provided args)
41
+ const promptResults = await runPrompts(initialContext);
42
+ // Build final context
43
+ const context = buildContext(options, promptResults);
44
+ // Validate compatibility
45
+ const validation = validateContext(context);
46
+ if (!validation.valid) {
47
+ logger.error(validation.error);
48
+ if (validation.suggestion) {
49
+ logger.info(`๐Ÿ’ก ${validation.suggestion}`);
50
+ }
51
+ process.exit(1);
52
+ }
53
+ logger.success('โœ“ Configuration valid!');
54
+ console.log('\nContext:', context);
55
+ // Track generation path for cleanup on interrupt
56
+ currentGenerationPath = context.projectPath;
57
+ // Generate project
58
+ logger.info('\n๐Ÿ—๏ธ Generating your Walrus application...\n');
59
+ const result = await generateProject({
60
+ context,
61
+ templateDir: join(__dirname, '../templates'),
62
+ targetDir: context.projectPath,
63
+ });
64
+ // Clear tracking after completion
65
+ currentGenerationPath = null;
66
+ if (!result.success) {
67
+ logger.error('โŒ Project generation failed');
68
+ process.exit(1);
69
+ }
70
+ // Post-install tasks
71
+ const postInstallResult = await runPostInstall({
72
+ context,
73
+ projectPath: context.projectPath,
74
+ skipInstall: options.skipInstall,
75
+ skipGit: options.skipGit,
76
+ skipValidation: options.skipValidation,
77
+ });
78
+ if (!postInstallResult.success) {
79
+ logger.warn('โš ๏ธ Post-install tasks completed with warnings');
80
+ }
81
+ }
82
+ catch (error) {
83
+ // Sanitize error messages - don't expose stack traces to users
84
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
85
+ logger.error(`Failed to create project: ${message}`);
86
+ process.exit(1);
87
+ }
88
+ });
89
+ // Handle cleanup on abort
90
+ process.on('SIGINT', async () => {
91
+ logger.warn('\n\nOperation cancelled by user.');
92
+ // Clean up partial generation if in progress
93
+ if (currentGenerationPath) {
94
+ logger.info(`๐Ÿงน Cleaning up partial generation: ${currentGenerationPath}`);
95
+ try {
96
+ await fs.remove(currentGenerationPath);
97
+ logger.success('โœ“ Cleanup completed');
98
+ }
99
+ catch (error) {
100
+ logger.error(`Failed to cleanup: ${error}`);
101
+ logger.warn(`โš ๏ธ Please manually delete: ${currentGenerationPath}`);
102
+ }
103
+ }
104
+ process.exit(0);
105
+ });
106
+ program.parse();
@@ -0,0 +1,31 @@
1
+ export declare const COMPATIBILITY_MATRIX: {
2
+ readonly mysten: {
3
+ readonly frameworks: readonly ["react", "vue", "plain-ts"];
4
+ readonly useCases: readonly ["simple-upload", "gallery", "defi-nft"];
5
+ };
6
+ readonly tusky: {
7
+ readonly frameworks: readonly ["react", "vue", "plain-ts"];
8
+ readonly useCases: readonly ["simple-upload", "gallery"];
9
+ };
10
+ readonly hibernuts: {
11
+ readonly frameworks: readonly ["react", "plain-ts"];
12
+ readonly useCases: readonly ["simple-upload"];
13
+ };
14
+ };
15
+ export declare const SDK_METADATA: {
16
+ readonly mysten: {
17
+ readonly name: "@mysten/walrus";
18
+ readonly description: "Official Mysten Labs SDK (Testnet stable)";
19
+ readonly docs: "https://docs.walrus.site";
20
+ };
21
+ readonly tusky: {
22
+ readonly name: "@tusky-io/ts-sdk";
23
+ readonly description: "Community TypeScript SDK";
24
+ readonly docs: "https://github.com/tusky-io";
25
+ };
26
+ readonly hibernuts: {
27
+ readonly name: "@hibernuts/walrus-sdk";
28
+ readonly description: "Alternative Walrus SDK";
29
+ readonly docs: "https://github.com/hibernuts";
30
+ };
31
+ };
package/dist/matrix.js ADDED
@@ -0,0 +1,31 @@
1
+ export const COMPATIBILITY_MATRIX = {
2
+ mysten: {
3
+ frameworks: ['react', 'vue', 'plain-ts'],
4
+ useCases: ['simple-upload', 'gallery', 'defi-nft'],
5
+ },
6
+ tusky: {
7
+ frameworks: ['react', 'vue', 'plain-ts'],
8
+ useCases: ['simple-upload', 'gallery'],
9
+ },
10
+ hibernuts: {
11
+ frameworks: ['react', 'plain-ts'],
12
+ useCases: ['simple-upload'],
13
+ },
14
+ };
15
+ export const SDK_METADATA = {
16
+ mysten: {
17
+ name: '@mysten/walrus',
18
+ description: 'Official Mysten Labs SDK (Testnet stable)',
19
+ docs: 'https://docs.walrus.site',
20
+ },
21
+ tusky: {
22
+ name: '@tusky-io/ts-sdk',
23
+ description: 'Community TypeScript SDK',
24
+ docs: 'https://github.com/tusky-io',
25
+ },
26
+ hibernuts: {
27
+ name: '@hibernuts/walrus-sdk',
28
+ description: 'Alternative Walrus SDK',
29
+ docs: 'https://github.com/hibernuts',
30
+ },
31
+ };
@@ -0,0 +1 @@
1
+ export {};