@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.
- package/dist/__tests__/helpers/adapter-compliance.d.ts +2 -0
- package/dist/__tests__/helpers/adapter-compliance.js +47 -0
- package/dist/__tests__/helpers/fixtures.d.ts +21 -0
- package/dist/__tests__/helpers/fixtures.js +30 -0
- package/dist/__tests__/helpers/fs-helpers.d.ts +12 -0
- package/dist/__tests__/helpers/fs-helpers.js +35 -0
- package/dist/__tests__/helpers/index.d.ts +4 -0
- package/dist/__tests__/helpers/index.js +4 -0
- package/dist/__tests__/helpers/test-hooks.d.ts +3 -0
- package/dist/__tests__/helpers/test-hooks.js +18 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +43 -0
- package/dist/context.test.d.ts +1 -0
- package/dist/context.test.js +98 -0
- package/dist/generator/file-ops.d.ts +12 -0
- package/dist/generator/file-ops.js +40 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +75 -0
- package/dist/generator/index.test.d.ts +1 -0
- package/dist/generator/index.test.js +143 -0
- package/dist/generator/layers.d.ts +3 -0
- package/dist/generator/layers.js +59 -0
- package/dist/generator/layers.test.d.ts +1 -0
- package/dist/generator/layers.test.js +92 -0
- package/dist/generator/merge.d.ts +14 -0
- package/dist/generator/merge.js +62 -0
- package/dist/generator/merge.test.d.ts +1 -0
- package/dist/generator/merge.test.js +79 -0
- package/dist/generator/transform.d.ts +21 -0
- package/dist/generator/transform.js +52 -0
- package/dist/generator/transform.test.d.ts +1 -0
- package/dist/generator/transform.test.js +51 -0
- package/dist/generator/types.d.ts +18 -0
- package/dist/generator/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +106 -0
- package/dist/matrix.d.ts +31 -0
- package/dist/matrix.js +31 -0
- package/dist/matrix.test.d.ts +1 -0
- package/dist/matrix.test.js +70 -0
- package/dist/post-install/git.d.ts +12 -0
- package/dist/post-install/git.js +94 -0
- package/dist/post-install/index.d.ts +16 -0
- package/dist/post-install/index.js +56 -0
- package/dist/post-install/messages.d.ts +9 -0
- package/dist/post-install/messages.js +49 -0
- package/dist/post-install/package-manager.d.ts +14 -0
- package/dist/post-install/package-manager.js +57 -0
- package/dist/post-install/validator.d.ts +14 -0
- package/dist/post-install/validator.js +114 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +115 -0
- package/dist/test-base.d.ts +1 -0
- package/dist/test-base.js +42 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/types.test.d.ts +1 -0
- package/dist/types.test.js +65 -0
- package/dist/utils/detect-pm.d.ts +2 -0
- package/dist/utils/detect-pm.js +10 -0
- package/dist/utils/detect-pm.test.d.ts +1 -0
- package/dist/utils/detect-pm.test.js +52 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +7 -0
- package/dist/validator.d.ts +3 -0
- package/dist/validator.js +48 -0
- package/dist/validator.test.d.ts +1 -0
- package/dist/validator.test.js +96 -0
- package/package.json +68 -0
- package/templates/base/.env.example +31 -0
- package/templates/base/README.md +54 -0
- package/templates/base/package.json +19 -0
- package/templates/base/src/adapters/storage.ts +58 -0
- package/templates/base/src/types/index.ts +9 -0
- package/templates/base/src/types/walrus.ts +22 -0
- package/templates/base/src/utils/env.ts +41 -0
- package/templates/base/src/utils/format.ts +29 -0
- package/templates/base/tsconfig.json +19 -0
- package/templates/gallery/README.md +44 -0
- package/templates/gallery/package.json +6 -0
- package/templates/gallery/src/App.tsx +21 -0
- package/templates/gallery/src/components/FileCard.tsx +27 -0
- package/templates/gallery/src/components/GalleryGrid.tsx +30 -0
- package/templates/gallery/src/components/UploadModal.tsx +45 -0
- package/templates/gallery/src/styles.css +58 -0
- package/templates/gallery/src/types/gallery.ts +13 -0
- package/templates/gallery/src/utils/index-manager.ts +37 -0
- package/templates/react/.eslintrc.json +26 -0
- package/templates/react/README.md +80 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +32 -0
- package/templates/react/src/App.tsx +14 -0
- package/templates/react/src/components/Layout.tsx +21 -0
- package/templates/react/src/components/WalletConnect.tsx +21 -0
- package/templates/react/src/dapp-kit.css +1 -0
- package/templates/react/src/hooks/useStorage.ts +40 -0
- package/templates/react/src/hooks/useWallet.ts +16 -0
- package/templates/react/src/index.css +50 -0
- package/templates/react/src/index.ts +10 -0
- package/templates/react/src/main.tsx +17 -0
- package/templates/react/src/providers/QueryProvider.tsx +18 -0
- package/templates/react/src/providers/WalletProvider.tsx +37 -0
- package/templates/react/tsconfig.json +27 -0
- package/templates/react/tsconfig.node.json +10 -0
- package/templates/react/vite.config.ts +19 -0
- package/templates/sdk-mysten/README.md +65 -0
- package/templates/sdk-mysten/package.json +14 -0
- package/templates/sdk-mysten/src/adapter.ts +80 -0
- package/templates/sdk-mysten/src/client.ts +45 -0
- package/templates/sdk-mysten/src/config.ts +33 -0
- package/templates/sdk-mysten/src/index.ts +11 -0
- package/templates/sdk-mysten/src/types.ts +19 -0
- package/templates/sdk-mysten/test/adapter.test.ts +20 -0
- package/templates/simple-upload/README.md +24 -0
- package/templates/simple-upload/package.json +6 -0
- package/templates/simple-upload/src/App.tsx +27 -0
- package/templates/simple-upload/src/components/FilePreview.tsx +40 -0
- package/templates/simple-upload/src/components/UploadForm.tsx +51 -0
- 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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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,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,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,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,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
|
+
}
|