create-pellicule 0.0.1

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/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "create-pellicule",
3
+ "version": "0.0.1",
4
+ "description": "Scaffold a new Pellicule video project",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-pellicule": "./src/index.js"
8
+ },
9
+ "keywords": [
10
+ "vue",
11
+ "video",
12
+ "rendering",
13
+ "pellicule",
14
+ "create",
15
+ "scaffold"
16
+ ],
17
+ "author": "Kelvin Omereshone <kelvin@sailscasts.com>",
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }
package/src/index.js ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from 'node:util'
4
+ import { resolve, join, dirname } from 'node:path'
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
9
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'))
10
+ const VERSION = pkg.version
11
+
12
+ // ANSI colors
13
+ const colors = {
14
+ reset: '\x1b[0m',
15
+ bold: '\x1b[1m',
16
+ dim: '\x1b[2m',
17
+ red: '\x1b[31m',
18
+ cyan: '\x1b[36m',
19
+ white: '\x1b[37m',
20
+ pellicule: '\x1b[38;2;66;184;131m',
21
+ bgPellicule: '\x1b[48;2;66;184;131m'
22
+ }
23
+
24
+ const c = {
25
+ error: (s) => `${colors.red}${s}${colors.reset}`,
26
+ info: (s) => `${colors.cyan}${s}${colors.reset}`,
27
+ dim: (s) => `${colors.dim}${s}${colors.reset}`,
28
+ bold: (s) => `${colors.bold}${s}${colors.reset}`,
29
+ highlight: (s) => `${colors.pellicule}${s}${colors.reset}`,
30
+ brand: (s) => `${colors.bgPellicule}${colors.white}${colors.bold}${s}${colors.reset}`
31
+ }
32
+
33
+ const HELP = `
34
+ ${c.bold('create-pellicule')} ${c.dim(`v${VERSION}`)} - Scaffold a new Pellicule project
35
+
36
+ ${c.bold('USAGE')}
37
+ ${c.highlight('npm create pellicule')} ${c.dim('→ create in current directory')}
38
+ ${c.highlight('npm create pellicule')} <name> ${c.dim('→ create in new directory')}
39
+
40
+ ${c.bold('OPTIONS')}
41
+ ${c.info('--help')} Show this help message
42
+ ${c.info('--version')} Show version number
43
+
44
+ ${c.bold('EXAMPLES')}
45
+ ${c.dim('# Create in current directory')}
46
+ ${c.highlight('npm create pellicule')}
47
+
48
+ ${c.dim('# Create in a new directory')}
49
+ ${c.highlight('npm create pellicule')} my-video
50
+
51
+ ${c.dim('Documentation: https://docs.sailscasts.com/pellicule')}
52
+ `
53
+
54
+ function copyTemplate(templateDir, targetDir, projectName) {
55
+ const files = readdirSync(templateDir)
56
+
57
+ for (const file of files) {
58
+ const srcPath = join(templateDir, file)
59
+ const destPath = join(targetDir, file)
60
+ let content = readFileSync(srcPath, 'utf-8')
61
+
62
+ // Replace template variables
63
+ content = content.replace(/\{\{name\}\}/g, projectName)
64
+
65
+ writeFileSync(destPath, content)
66
+ }
67
+ }
68
+
69
+ async function main() {
70
+ const { values, positionals } = parseArgs({
71
+ allowPositionals: true,
72
+ options: {
73
+ help: { type: 'boolean' },
74
+ version: { type: 'boolean' }
75
+ }
76
+ })
77
+
78
+ if (values.help) {
79
+ console.log(HELP)
80
+ process.exit(0)
81
+ }
82
+
83
+ if (values.version) {
84
+ console.log(VERSION)
85
+ process.exit(0)
86
+ }
87
+
88
+ const projectName = positionals[0] || '.'
89
+ const targetDir = resolve(projectName)
90
+ const isCurrentDir = projectName === '.'
91
+
92
+ console.log()
93
+ console.log(` ${c.brand(' PELLICULE ')} ${c.dim('Create')}`)
94
+ console.log()
95
+
96
+ // Check if directory exists and has files
97
+ if (existsSync(targetDir)) {
98
+ const files = readdirSync(targetDir)
99
+ const hasFiles = files.filter(f => !f.startsWith('.')).length > 0
100
+ if (hasFiles && !isCurrentDir) {
101
+ console.error(c.error(` Error: Directory "${projectName}" already exists and is not empty.\n`))
102
+ process.exit(1)
103
+ }
104
+ } else {
105
+ mkdirSync(targetDir, { recursive: true })
106
+ }
107
+
108
+ // Copy template files
109
+ const templateDir = resolve(__dirname, '../template')
110
+ const displayName = isCurrentDir ? 'current directory' : projectName
111
+
112
+ console.log(` ${c.highlight('Scaffolding project')} in ${c.info(displayName)}...`)
113
+ console.log()
114
+
115
+ copyTemplate(templateDir, targetDir, isCurrentDir ? 'my-pellicule-video' : projectName)
116
+
117
+ // Success message
118
+ console.log(` ${c.highlight('Done!')} Created Pellicule project.`)
119
+ console.log()
120
+ console.log(` ${c.bold('Next steps:')}`)
121
+ console.log()
122
+ if (!isCurrentDir) {
123
+ console.log(` ${c.dim('1.')} cd ${projectName}`)
124
+ console.log(` ${c.dim('2.')} npm install`)
125
+ console.log(` ${c.dim('3.')} npx pellicule`)
126
+ } else {
127
+ console.log(` ${c.dim('1.')} npm install`)
128
+ console.log(` ${c.dim('2.')} npx pellicule`)
129
+ }
130
+ console.log()
131
+ console.log(` ${c.dim('Documentation:')} https://docs.sailscasts.com/pellicule`)
132
+ console.log()
133
+ }
134
+
135
+ main().catch((error) => {
136
+ console.error(c.error(`\nError: ${error.message}\n`))
137
+ process.exit(1)
138
+ })
@@ -0,0 +1,97 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+ import { useFrame, useVideoConfig, interpolate, Easing } from 'pellicule'
4
+
5
+ const frame = useFrame()
6
+ const { width, height, fps, durationInFrames } = useVideoConfig()
7
+
8
+ // Animation: fade in and scale up
9
+ const opacity = computed(() =>
10
+ interpolate(frame.value, [0, 30], [0, 1])
11
+ )
12
+
13
+ const scale = computed(() =>
14
+ interpolate(frame.value, [0, 30], [0.8, 1], { easing: Easing.easeOut })
15
+ )
16
+
17
+ // Animation: slide up the subtitle
18
+ const subtitleY = computed(() =>
19
+ interpolate(frame.value, [20, 50], [40, 0], { easing: Easing.easeOut })
20
+ )
21
+
22
+ const subtitleOpacity = computed(() =>
23
+ interpolate(frame.value, [20, 50], [0, 1])
24
+ )
25
+ </script>
26
+
27
+ <template>
28
+ <div class="video">
29
+ <!-- Main content -->
30
+ <div
31
+ class="content"
32
+ :style="{
33
+ opacity,
34
+ transform: `scale(${scale})`
35
+ }"
36
+ >
37
+ <h1 class="title">Hello, Pellicule!</h1>
38
+
39
+ <p
40
+ class="subtitle"
41
+ :style="{
42
+ opacity: subtitleOpacity,
43
+ transform: `translateY(${subtitleY}px)`
44
+ }"
45
+ >
46
+ Vue-native programmatic video
47
+ </p>
48
+ </div>
49
+
50
+ <!-- Frame counter (helpful for debugging) -->
51
+ <div class="frame-info">
52
+ Frame {{ frame }} / {{ durationInFrames }}
53
+ </div>
54
+ </div>
55
+ </template>
56
+
57
+ <style scoped>
58
+ .video {
59
+ width: 100%;
60
+ height: 100%;
61
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
62
+ display: flex;
63
+ flex-direction: column;
64
+ align-items: center;
65
+ justify-content: center;
66
+ font-family: system-ui, -apple-system, sans-serif;
67
+ color: white;
68
+ }
69
+
70
+ .content {
71
+ text-align: center;
72
+ }
73
+
74
+ .title {
75
+ font-size: 72px;
76
+ font-weight: 700;
77
+ margin: 0;
78
+ background: linear-gradient(135deg, #42b883 0%, #6ee7a0 100%);
79
+ -webkit-background-clip: text;
80
+ -webkit-text-fill-color: transparent;
81
+ background-clip: text;
82
+ }
83
+
84
+ .subtitle {
85
+ font-size: 32px;
86
+ color: rgba(255, 255, 255, 0.7);
87
+ margin-top: 20px;
88
+ }
89
+
90
+ .frame-info {
91
+ position: absolute;
92
+ bottom: 40px;
93
+ font-size: 16px;
94
+ color: rgba(255, 255, 255, 0.3);
95
+ font-family: monospace;
96
+ }
97
+ </style>
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "render": "npx pellicule"
7
+ },
8
+ "dependencies": {
9
+ "pellicule": "^0.0.1",
10
+ "vue": "^3.4.0"
11
+ }
12
+ }