@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,42 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ const basePath = path.join(__dirname, '../../../templates/base');
7
+ const requiredFiles = [
8
+ 'src/adapters/storage.ts',
9
+ 'src/types/walrus.ts',
10
+ 'src/types/index.ts',
11
+ 'src/utils/env.ts',
12
+ 'src/utils/format.ts',
13
+ '.env.example',
14
+ '.gitignore',
15
+ 'package.json',
16
+ 'tsconfig.json',
17
+ 'README.md',
18
+ ];
19
+ let passed = 0;
20
+ let failed = 0;
21
+ console.log('🔍 Validating base template structure...\n');
22
+ for (const file of requiredFiles) {
23
+ const fullPath = path.join(basePath, file);
24
+ const exists = fs.existsSync(fullPath);
25
+ if (exists) {
26
+ console.log(`✓ ${file}`);
27
+ passed++;
28
+ }
29
+ else {
30
+ console.log(`✗ MISSING: ${file}`);
31
+ failed++;
32
+ }
33
+ }
34
+ console.log(`\n${passed}/${requiredFiles.length} files found`);
35
+ if (failed > 0) {
36
+ console.error(`\n❌ Validation failed: ${failed} files missing`);
37
+ process.exit(1);
38
+ }
39
+ else {
40
+ console.log('\n✅ Base layer validation passed!');
41
+ process.exit(0);
42
+ }
@@ -0,0 +1,19 @@
1
+ export type SDK = 'mysten' | 'tusky' | 'hibernuts';
2
+ export type Framework = 'react' | 'vue' | 'plain-ts';
3
+ export type UseCase = 'simple-upload' | 'gallery' | 'defi-nft';
4
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
5
+ export interface Context {
6
+ projectName: string;
7
+ projectPath: string;
8
+ sdk: SDK;
9
+ framework: Framework;
10
+ useCase: UseCase;
11
+ analytics: boolean;
12
+ tailwind: boolean;
13
+ packageManager: PackageManager;
14
+ }
15
+ export interface ValidationResult {
16
+ valid: boolean;
17
+ error?: string;
18
+ suggestion?: string;
19
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('Type definitions', () => {
3
+ it('should allow valid SDK values', () => {
4
+ const sdks = ['mysten', 'tusky', 'hibernuts'];
5
+ expect(sdks).toHaveLength(3);
6
+ });
7
+ it('should allow valid Framework values', () => {
8
+ const frameworks = ['react', 'vue', 'plain-ts'];
9
+ expect(frameworks).toHaveLength(3);
10
+ });
11
+ it('should allow valid UseCase values', () => {
12
+ const useCases = ['simple-upload', 'gallery', 'defi-nft'];
13
+ expect(useCases).toHaveLength(3);
14
+ });
15
+ it('should allow valid PackageManager values', () => {
16
+ const pms = ['npm', 'pnpm', 'yarn', 'bun'];
17
+ expect(pms).toHaveLength(4);
18
+ });
19
+ it('should create valid Context object', () => {
20
+ const context = {
21
+ projectName: 'test-app',
22
+ projectPath: '/path/to/test-app',
23
+ sdk: 'mysten',
24
+ framework: 'react',
25
+ useCase: 'simple-upload',
26
+ analytics: false,
27
+ tailwind: true,
28
+ packageManager: 'pnpm',
29
+ };
30
+ expect(context.projectName).toBe('test-app');
31
+ expect(context.sdk).toBe('mysten');
32
+ expect(context.framework).toBe('react');
33
+ expect(context.useCase).toBe('simple-upload');
34
+ expect(context.analytics).toBe(false);
35
+ expect(context.tailwind).toBe(true);
36
+ expect(context.packageManager).toBe('pnpm');
37
+ });
38
+ it('should create valid ValidationResult with success', () => {
39
+ const result = {
40
+ valid: true,
41
+ };
42
+ expect(result.valid).toBe(true);
43
+ expect(result.error).toBeUndefined();
44
+ expect(result.suggestion).toBeUndefined();
45
+ });
46
+ it('should create valid ValidationResult with error', () => {
47
+ const result = {
48
+ valid: false,
49
+ error: 'Something went wrong',
50
+ suggestion: 'Try this instead',
51
+ };
52
+ expect(result.valid).toBe(false);
53
+ expect(result.error).toBe('Something went wrong');
54
+ expect(result.suggestion).toBe('Try this instead');
55
+ });
56
+ it('should allow ValidationResult with error but no suggestion', () => {
57
+ const result = {
58
+ valid: false,
59
+ error: 'Error without suggestion',
60
+ };
61
+ expect(result.valid).toBe(false);
62
+ expect(result.error).toBe('Error without suggestion');
63
+ expect(result.suggestion).toBeUndefined();
64
+ });
65
+ });
@@ -0,0 +1,2 @@
1
+ import { PackageManager } from '../types.js';
2
+ export declare function detectPackageManager(): PackageManager;
@@ -0,0 +1,10 @@
1
+ export function detectPackageManager() {
2
+ const userAgent = process.env.npm_config_user_agent;
3
+ if (userAgent?.includes('pnpm'))
4
+ return 'pnpm';
5
+ if (userAgent?.includes('yarn'))
6
+ return 'yarn';
7
+ if (userAgent?.includes('bun'))
8
+ return 'bun';
9
+ return 'npm';
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { detectPackageManager } from './detect-pm.js';
3
+ describe('detectPackageManager', () => {
4
+ let originalUserAgent;
5
+ beforeEach(() => {
6
+ originalUserAgent = process.env.npm_config_user_agent;
7
+ });
8
+ afterEach(() => {
9
+ if (originalUserAgent === undefined) {
10
+ delete process.env.npm_config_user_agent;
11
+ }
12
+ else {
13
+ process.env.npm_config_user_agent = originalUserAgent;
14
+ }
15
+ });
16
+ it('should detect pnpm from user agent', () => {
17
+ process.env.npm_config_user_agent = 'pnpm/8.6.0 npm/? node/v18.16.0 linux x64';
18
+ expect(detectPackageManager()).toBe('pnpm');
19
+ });
20
+ it('should detect yarn from user agent', () => {
21
+ process.env.npm_config_user_agent = 'yarn/1.22.19 npm/? node/v18.16.0 linux x64';
22
+ expect(detectPackageManager()).toBe('yarn');
23
+ });
24
+ it('should detect bun from user agent', () => {
25
+ process.env.npm_config_user_agent = 'bun/1.0.0';
26
+ expect(detectPackageManager()).toBe('bun');
27
+ });
28
+ it('should default to npm when no user agent', () => {
29
+ delete process.env.npm_config_user_agent;
30
+ expect(detectPackageManager()).toBe('npm');
31
+ });
32
+ it('should default to npm for unknown user agent', () => {
33
+ process.env.npm_config_user_agent = 'unknown/1.0.0';
34
+ expect(detectPackageManager()).toBe('npm');
35
+ });
36
+ it('should handle user agent with pnpm in different positions', () => {
37
+ process.env.npm_config_user_agent = 'npm/? pnpm/8.0.0 node/v18.0.0';
38
+ expect(detectPackageManager()).toBe('pnpm');
39
+ });
40
+ it('should prioritize pnpm over yarn when both present', () => {
41
+ process.env.npm_config_user_agent = 'pnpm/8.0.0 yarn/1.22.0';
42
+ expect(detectPackageManager()).toBe('pnpm');
43
+ });
44
+ it('should prioritize yarn over bun when both present', () => {
45
+ process.env.npm_config_user_agent = 'yarn/1.22.0 bun/1.0.0';
46
+ expect(detectPackageManager()).toBe('yarn');
47
+ });
48
+ it('should handle empty string user agent', () => {
49
+ process.env.npm_config_user_agent = '';
50
+ expect(detectPackageManager()).toBe('npm');
51
+ });
52
+ });
@@ -0,0 +1,6 @@
1
+ export declare const logger: {
2
+ info: (msg: string) => void;
3
+ success: (msg: string) => void;
4
+ error: (msg: string) => void;
5
+ warn: (msg: string) => void;
6
+ };
@@ -0,0 +1,7 @@
1
+ import kleur from 'kleur';
2
+ export const logger = {
3
+ info: (msg) => console.log(kleur.blue('ℹ'), msg),
4
+ success: (msg) => console.log(kleur.green('✓'), msg),
5
+ error: (msg) => console.error(kleur.red('✗'), msg),
6
+ warn: (msg) => console.warn(kleur.yellow('⚠'), msg),
7
+ };
@@ -0,0 +1,3 @@
1
+ import { Context, ValidationResult } from './types.js';
2
+ export declare function validateContext(context: Context): ValidationResult;
3
+ export declare function validateProjectName(name: string): boolean | string;
@@ -0,0 +1,48 @@
1
+ import path from 'node:path';
2
+ import { COMPATIBILITY_MATRIX } from './matrix.js';
3
+ export function validateContext(context) {
4
+ const { sdk, framework, useCase } = context;
5
+ // Check framework compatibility
6
+ if (!COMPATIBILITY_MATRIX[sdk].frameworks.includes(framework)) {
7
+ return {
8
+ valid: false,
9
+ error: `SDK "${sdk}" is incompatible with framework "${framework}"`,
10
+ suggestion: `Compatible frameworks for ${sdk}: ${COMPATIBILITY_MATRIX[sdk].frameworks.join(', ')}`,
11
+ };
12
+ }
13
+ // Check use case compatibility
14
+ if (!COMPATIBILITY_MATRIX[sdk].useCases.includes(useCase)) {
15
+ return {
16
+ valid: false,
17
+ error: `SDK "${sdk}" does not support use case "${useCase}"`,
18
+ suggestion: `Supported use cases for ${sdk}: ${COMPATIBILITY_MATRIX[sdk].useCases.join(', ')}`,
19
+ };
20
+ }
21
+ return { valid: true };
22
+ }
23
+ export function validateProjectName(name) {
24
+ // Check for empty string
25
+ if (!name || name.trim().length === 0) {
26
+ return 'Project name cannot be empty';
27
+ }
28
+ // npm package name length limit (214 characters)
29
+ if (name.length > 214) {
30
+ return 'Project name must be 214 characters or less';
31
+ }
32
+ // Prevent path traversal
33
+ if (name.includes('..') || name.includes('/') || name.includes('\\')) {
34
+ return 'Project name cannot contain path separators';
35
+ }
36
+ // Prevent absolute paths
37
+ if (path.isAbsolute(name)) {
38
+ return 'Project name cannot be an absolute path';
39
+ }
40
+ // npm package naming rules
41
+ if (!/^[a-z0-9-]+$/.test(name)) {
42
+ return 'Project name must contain only lowercase letters, numbers, and hyphens';
43
+ }
44
+ if (name.startsWith('-') || name.endsWith('-')) {
45
+ return 'Project name cannot start or end with a hyphen';
46
+ }
47
+ return true;
48
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateProjectName, validateContext } from './validator.js';
3
+ describe('validateProjectName', () => {
4
+ it('should accept valid project names', () => {
5
+ expect(validateProjectName('my-project')).toBe(true);
6
+ expect(validateProjectName('app123')).toBe(true);
7
+ expect(validateProjectName('walrus-app-v2')).toBe(true);
8
+ });
9
+ it('should reject names with path traversal', () => {
10
+ expect(validateProjectName('../my-project')).toContain('path separators');
11
+ expect(validateProjectName('../../escape')).toContain('path separators');
12
+ expect(validateProjectName('test/../bad')).toContain('path separators');
13
+ });
14
+ it('should reject names with forward slashes', () => {
15
+ expect(validateProjectName('my/project')).toContain('path separators');
16
+ expect(validateProjectName('/absolute/path')).toContain('path separators');
17
+ });
18
+ it('should reject names with backslashes', () => {
19
+ expect(validateProjectName('my\\project')).toContain('path separators');
20
+ expect(validateProjectName('C:\\Windows\\path')).toContain('path separators');
21
+ });
22
+ it('should reject absolute paths', () => {
23
+ expect(validateProjectName('/usr/local/bin')).toContain('path');
24
+ expect(validateProjectName('C:\\Program Files')).toContain('path');
25
+ });
26
+ it('should reject names with uppercase letters', () => {
27
+ expect(validateProjectName('MyProject')).toContain('lowercase');
28
+ expect(validateProjectName('TEST')).toContain('lowercase');
29
+ });
30
+ it('should reject names with special characters', () => {
31
+ expect(validateProjectName('my_project')).toContain('lowercase');
32
+ expect(validateProjectName('my.project')).toContain('lowercase');
33
+ expect(validateProjectName('my@project')).toContain('lowercase');
34
+ expect(validateProjectName('my project')).toContain('lowercase');
35
+ });
36
+ it('should reject names starting with hyphen', () => {
37
+ expect(validateProjectName('-myproject')).toContain('hyphen');
38
+ });
39
+ it('should reject names ending with hyphen', () => {
40
+ expect(validateProjectName('myproject-')).toContain('hyphen');
41
+ });
42
+ it('should accept names with hyphens in middle', () => {
43
+ expect(validateProjectName('my-awesome-project')).toBe(true);
44
+ });
45
+ it('should accept numbers in names', () => {
46
+ expect(validateProjectName('app123')).toBe(true);
47
+ expect(validateProjectName('2024-project')).toBe(true);
48
+ });
49
+ });
50
+ describe('validateContext', () => {
51
+ const createContext = (overrides = {}) => ({
52
+ projectName: 'test-app',
53
+ projectPath: '/path/to/test-app',
54
+ sdk: 'mysten',
55
+ framework: 'react',
56
+ useCase: 'simple-upload',
57
+ analytics: false,
58
+ tailwind: true,
59
+ packageManager: 'pnpm',
60
+ ...overrides,
61
+ });
62
+ it('should validate compatible sdk and framework combinations', () => {
63
+ expect(validateContext(createContext({ sdk: 'mysten', framework: 'react' }))).toEqual({ valid: true });
64
+ expect(validateContext(createContext({ sdk: 'mysten', framework: 'vue' }))).toEqual({ valid: true });
65
+ expect(validateContext(createContext({ sdk: 'mysten', framework: 'plain-ts' }))).toEqual({ valid: true });
66
+ expect(validateContext(createContext({ sdk: 'tusky', framework: 'react' }))).toEqual({ valid: true });
67
+ expect(validateContext(createContext({ sdk: 'hibernuts', framework: 'react' }))).toEqual({ valid: true });
68
+ });
69
+ it('should reject incompatible sdk and framework combinations', () => {
70
+ const result = validateContext(createContext({ sdk: 'hibernuts', framework: 'vue' }));
71
+ expect(result.valid).toBe(false);
72
+ expect(result.error).toContain('incompatible');
73
+ expect(result.suggestion).toContain('Compatible frameworks');
74
+ });
75
+ it('should validate compatible sdk and useCase combinations', () => {
76
+ expect(validateContext(createContext({ sdk: 'mysten', useCase: 'simple-upload' }))).toEqual({ valid: true });
77
+ expect(validateContext(createContext({ sdk: 'mysten', useCase: 'gallery' }))).toEqual({ valid: true });
78
+ expect(validateContext(createContext({ sdk: 'mysten', useCase: 'defi-nft' }))).toEqual({ valid: true });
79
+ expect(validateContext(createContext({ sdk: 'tusky', useCase: 'simple-upload' }))).toEqual({ valid: true });
80
+ });
81
+ it('should reject incompatible sdk and useCase combinations', () => {
82
+ const result = validateContext(createContext({ sdk: 'tusky', useCase: 'defi-nft' }));
83
+ expect(result.valid).toBe(false);
84
+ expect(result.error).toContain('does not support');
85
+ expect(result.suggestion).toContain('Supported use cases');
86
+ });
87
+ it('should reject hibernuts with gallery use case', () => {
88
+ const result = validateContext(createContext({ sdk: 'hibernuts', useCase: 'gallery' }));
89
+ expect(result.valid).toBe(false);
90
+ expect(result.error).toBeDefined();
91
+ });
92
+ it('should provide helpful suggestions in error messages', () => {
93
+ const result = validateContext(createContext({ sdk: 'hibernuts', framework: 'vue' }));
94
+ expect(result.suggestion).toMatch(/react|plain-ts/);
95
+ });
96
+ });
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@blu1606/create-walrus-app",
3
+ "version": "0.1.0",
4
+ "description": "Interactive CLI for scaffolding Walrus applications",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-walrus-app": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest",
18
+ "test:ui": "vitest --ui",
19
+ "test:coverage": "vitest run --coverage",
20
+ "test:integration": "node tests/integration/integration.test.mjs",
21
+ "test:validation": "node tests/integration/validation.test.mjs",
22
+ "test:manual": "node tests/integration/manual.test.js",
23
+ "test:e2e": "node tests/integration/cli.e2e.test.mjs",
24
+ "test:all": "vitest run && pnpm test:e2e",
25
+ "prepublishOnly": "pnpm build"
26
+ },
27
+ "keywords": [
28
+ "walrus",
29
+ "sui",
30
+ "scaffold",
31
+ "cli",
32
+ "template"
33
+ ],
34
+ "author": "blu1606 dongthanhquandtq@gmail.com",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/blu1606/walrus-starter-kit.git",
39
+ "directory": "packages/cli"
40
+ },
41
+ "dependencies": {
42
+ "commander": "^11.1.0",
43
+ "cross-spawn": "^7.0.3",
44
+ "fs-extra": "^11.2.0",
45
+ "kleur": "^4.1.5",
46
+ "prompts": "^2.4.2",
47
+ "sort-package-json": "^2.10.1"
48
+ },
49
+ "devDependencies": {
50
+ "@types/cross-spawn": "^6.0.6",
51
+ "@types/fs-extra": "^11.0.4",
52
+ "@types/node": "^20.11.0",
53
+ "@types/prompts": "^2.4.9",
54
+ "@vitest/coverage-v8": "^4.0.17",
55
+ "@vitest/ui": "^4.0.17",
56
+ "execa": "^9.5.2",
57
+ "strip-ansi": "^7.1.0",
58
+ "typescript": "^5.3.0",
59
+ "vitest": "^4.0.17"
60
+ },
61
+ "engines": {
62
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0",
63
+ "pnpm": ">=9.0.0"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ }
68
+ }
@@ -0,0 +1,31 @@
1
+ ## ==============================================
2
+ ## Walrus Application - Environment Configuration
3
+ ## ==============================================
4
+
5
+ ## WALRUS NETWORK SETTINGS
6
+ ## Network: testnet | mainnet | devnet
7
+ VITE_WALRUS_NETWORK=testnet
8
+
9
+ ## Walrus Aggregator URL (for downloads)
10
+ VITE_WALRUS_AGGREGATOR=https://aggregator.walrus-testnet.walrus.space
11
+
12
+ ## Walrus Publisher URL (for uploads)
13
+ VITE_WALRUS_PUBLISHER=https://publisher.walrus-testnet.walrus.space
14
+
15
+ ## SUI BLOCKCHAIN SETTINGS
16
+ ## Sui Network: testnet | mainnet | devnet
17
+ VITE_SUI_NETWORK=testnet
18
+
19
+ ## Sui RPC URL (for wallet interactions)
20
+ VITE_SUI_RPC=https://fullnode.testnet.sui.io:443
21
+
22
+ ## OPTIONAL FEATURES
23
+ ## Blockberry Analytics API Key (leave empty to disable)
24
+ VITE_BLOCKBERRY_KEY=
25
+
26
+ ## ==============================================
27
+ ## PREREQUISITES
28
+ ## ==============================================
29
+ ## 1. Install Sui Wallet browser extension
30
+ ## 2. Get testnet SUI from faucet: https://faucet.testnet.sui.io/
31
+ ## 3. Copy this file to .env and fill in any optional values
@@ -0,0 +1,54 @@
1
+ # {{projectName}}
2
+
3
+ This is a Walrus application generated by `create-walrus-app`.
4
+
5
+ ## What's Included
6
+
7
+ ### Adapter Interface
8
+
9
+ - `src/adapters/storage.ts` - Universal SDK-agnostic interface
10
+ - Allows use case code to work with any Walrus SDK
11
+
12
+ ### Type Definitions
13
+
14
+ - `src/types/walrus.ts` - Walrus-specific types
15
+ - `src/types/index.ts` - Common utility types
16
+
17
+ ### Utilities
18
+
19
+ - `src/utils/env.ts` - Environment validation
20
+ - `src/utils/format.ts` - Formatting helpers
21
+
22
+ ### Configuration
23
+
24
+ - `.env.example` - Environment template
25
+ - `tsconfig.json` - TypeScript strict mode config
26
+ - `package.json` - Base dependencies
27
+
28
+ ## Layer Composition
29
+
30
+ This base layer is **always included** and combined with:
31
+
32
+ 1. **SDK Layer** (e.g., `sdk-mysten/`) - Implements `StorageAdapter`
33
+ 2. **Framework Layer** (e.g., `react/`) - UI framework setup
34
+ 3. **Use Case Layer** (e.g., `simple-upload/`) - Application logic
35
+
36
+ ```
37
+ Base + SDK + Framework + UseCase = Your App
38
+ ```
39
+
40
+ ## Environment Setup
41
+
42
+ 1. Copy `.env.example` to `.env`
43
+ 2. Fill in required values:
44
+ - Walrus network URLs
45
+ - Sui RPC endpoint
46
+ 3. Optional: Add Blockberry API key
47
+
48
+ ## Next Steps
49
+
50
+ This base layer is completed by:
51
+
52
+ - **Phase 4**: SDK implementation
53
+ - **Phase 5**: Framework setup
54
+ - **Phase 6**: Use case logic
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "echo 'Override by framework layer'",
8
+ "build": "echo 'Override by framework layer'",
9
+ "preview": "echo 'Override by framework layer'",
10
+ "lint": "eslint . --ext .ts,.tsx",
11
+ "type-check": "tsc --noEmit"
12
+ },
13
+ "devDependencies": {
14
+ "typescript": "^5.3.3",
15
+ "eslint": "^8.56.0",
16
+ "@typescript-eslint/parser": "^6.19.1",
17
+ "@typescript-eslint/eslint-plugin": "^6.19.1"
18
+ }
19
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Universal storage adapter interface for Walrus
3
+ *
4
+ * This interface abstracts SDK-specific implementations,
5
+ * allowing use case layers to work with any Walrus SDK.
6
+ */
7
+
8
+ export interface BlobMetadata {
9
+ blobId: string;
10
+ size: number;
11
+ contentType?: string;
12
+ createdAt: number;
13
+ expiresAt?: number;
14
+ }
15
+
16
+ export interface UploadOptions {
17
+ /** Number of epochs to store (Walrus-specific) */
18
+ epochs?: number;
19
+ /** MIME type of the content */
20
+ contentType?: string;
21
+ }
22
+
23
+ export interface DownloadOptions {
24
+ /** Byte range (for large files) */
25
+ range?: { start: number; end: number };
26
+ }
27
+
28
+ export interface StorageAdapter {
29
+ /**
30
+ * Upload data to Walrus storage
31
+ * @param data - File or raw bytes to upload
32
+ * @param options - Upload configuration
33
+ * @returns Blob ID (permanent reference)
34
+ */
35
+ upload(data: File | Uint8Array, options?: UploadOptions): Promise<string>;
36
+
37
+ /**
38
+ * Download blob data by ID
39
+ * @param blobId - Unique blob identifier
40
+ * @param options - Download configuration
41
+ * @returns Raw blob data
42
+ */
43
+ download(blobId: string, options?: DownloadOptions): Promise<Uint8Array>;
44
+
45
+ /**
46
+ * Get blob metadata without downloading content
47
+ * @param blobId - Unique blob identifier
48
+ * @returns Metadata object
49
+ */
50
+ getMetadata(blobId: string): Promise<BlobMetadata>;
51
+
52
+ /**
53
+ * Check if blob exists
54
+ * @param blobId - Unique blob identifier
55
+ * @returns True if blob is accessible
56
+ */
57
+ exists(blobId: string): Promise<boolean>;
58
+ }
@@ -0,0 +1,9 @@
1
+ export * from './walrus.js';
2
+
3
+ export interface Result<T, E = Error> {
4
+ success: boolean;
5
+ data?: T;
6
+ error?: E;
7
+ }
8
+
9
+ export type AsyncResult<T, E = Error> = Promise<Result<T, E>>;
@@ -0,0 +1,22 @@
1
+ export type WalrusNetwork = 'testnet' | 'mainnet' | 'devnet';
2
+
3
+ export interface WalrusConfig {
4
+ network: WalrusNetwork;
5
+ publisherUrl: string;
6
+ aggregatorUrl: string;
7
+ suiRpcUrl: string;
8
+ }
9
+
10
+ export interface BlobInfo {
11
+ blobId: string;
12
+ name?: string;
13
+ size: number;
14
+ contentType?: string;
15
+ uploadedAt: number;
16
+ }
17
+
18
+ export interface StorageStats {
19
+ totalBlobs: number;
20
+ totalSize: number;
21
+ usedEpochs: number;
22
+ }