@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,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 {};
|
package/dist/index.d.ts
ADDED
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();
|
package/dist/matrix.d.ts
ADDED
|
@@ -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 {};
|