fragment-tools 0.1.13 → 0.1.15

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 (184) hide show
  1. package/.prettierignore +1 -2
  2. package/.prettierrc +23 -7
  3. package/README.md +28 -9
  4. package/bin/index.js +70 -10
  5. package/package.json +14 -6
  6. package/src/cli/build.js +125 -0
  7. package/src/cli/create.js +238 -0
  8. package/src/cli/createConfig.js +82 -0
  9. package/src/cli/createFragmentFile.js +70 -0
  10. package/src/cli/getEntries.js +85 -0
  11. package/src/cli/log.js +36 -24
  12. package/src/cli/plugins/check-dependencies.js +88 -42
  13. package/src/cli/plugins/hot-shader-replacement.js +408 -0
  14. package/src/cli/plugins/hot-sketch-reload.js +21 -25
  15. package/src/cli/plugins/save.js +101 -0
  16. package/src/cli/preview.js +55 -0
  17. package/src/cli/prompts.js +260 -0
  18. package/src/cli/run.js +131 -0
  19. package/src/cli/templates/blank/index.js +33 -0
  20. package/src/cli/templates/blank/meta.json +4 -0
  21. package/src/cli/templates/default/index.js +39 -0
  22. package/src/cli/templates/default/meta.json +5 -0
  23. package/src/cli/templates/fragment-gl/index.js +37 -0
  24. package/src/cli/templates/fragment-gl/meta.json +4 -0
  25. package/src/cli/templates/p5/index.js +32 -0
  26. package/src/cli/templates/p5/meta.json +5 -0
  27. package/src/cli/templates/p5-webgl/fragment.fs +14 -0
  28. package/src/cli/templates/p5-webgl/index.js +67 -0
  29. package/src/cli/templates/p5-webgl/meta.json +5 -0
  30. package/src/cli/templates/three-fragment/fragment.fs +10 -0
  31. package/src/cli/templates/three-fragment/index.js +95 -0
  32. package/src/cli/templates/three-fragment/meta.json +5 -0
  33. package/src/cli/templates/three-orthographic/index.js +55 -0
  34. package/src/cli/templates/three-orthographic/meta.json +5 -0
  35. package/src/cli/templates/three-perspective/index.js +52 -0
  36. package/src/cli/templates/three-perspective/meta.json +5 -0
  37. package/src/cli/utils.js +70 -0
  38. package/src/cli/ws.js +87 -78
  39. package/src/client/app/App.svelte +3 -3
  40. package/src/client/app/client.js +55 -39
  41. package/src/client/app/components/IconCross.svelte +18 -18
  42. package/src/client/app/components/Init.svelte +40 -8
  43. package/src/client/app/components/KeyBinding.svelte +22 -22
  44. package/src/client/app/helpers.js +42 -0
  45. package/src/client/app/hooks.js +20 -0
  46. package/src/client/app/inputs/Input.js +9 -9
  47. package/src/client/app/inputs/Keyboard.js +13 -15
  48. package/src/client/app/inputs/MIDI.js +14 -15
  49. package/src/client/app/inputs/Mouse.js +1 -1
  50. package/src/client/app/inputs/Webcam.js +89 -88
  51. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +41 -21
  52. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +7 -6
  53. package/src/client/app/lib/canvas-recorder/H264Recorder.js +45 -0
  54. package/src/client/app/lib/canvas-recorder/MP4Recorder.js +7 -9
  55. package/src/client/app/lib/canvas-recorder/WebMRecorder.js +3 -4
  56. package/src/client/app/lib/canvas-recorder/mp4.js +1649 -15
  57. package/src/client/app/lib/canvas-recorder/utils.js +33 -17
  58. package/src/client/app/lib/gl/Geometry.js +11 -8
  59. package/src/client/app/lib/gl/Program.js +38 -19
  60. package/src/client/app/lib/gl/Renderer.js +163 -156
  61. package/src/client/app/lib/gl/Texture.js +113 -85
  62. package/src/client/app/lib/gl/index.js +12 -12
  63. package/src/client/app/lib/gl/utils.js +1 -3
  64. package/src/client/app/lib/helpers/frameDebounce.js +30 -30
  65. package/src/client/app/lib/loader/index.js +10 -10
  66. package/src/client/app/lib/loader/loadImage.js +15 -15
  67. package/src/client/app/lib/loader/loadScript.js +1 -1
  68. package/src/client/app/lib/paper-sizes.js +75 -76
  69. package/src/client/app/lib/presets.js +25 -5
  70. package/src/client/app/lib/tempo/Analyser.js +18 -17
  71. package/src/client/app/lib/tempo/Range.js +15 -12
  72. package/src/client/app/lib/tempo/index.js +34 -27
  73. package/src/client/app/modules/AudioAnalyser/Range.svelte +69 -72
  74. package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +20 -19
  75. package/src/client/app/modules/AudioAnalyser.svelte +52 -35
  76. package/src/client/app/modules/Console/ConsoleLine.svelte +193 -172
  77. package/src/client/app/modules/Console.svelte +76 -74
  78. package/src/client/app/modules/Exports.svelte +62 -43
  79. package/src/client/app/modules/MidiPanel.svelte +100 -101
  80. package/src/client/app/modules/Monitor.svelte +57 -57
  81. package/src/client/app/modules/Params.svelte +128 -103
  82. package/src/client/app/renderers/2DRenderer.js +3 -3
  83. package/src/client/app/renderers/FragmentRenderer.js +30 -23
  84. package/src/client/app/renderers/P5GLRenderer.js +144 -0
  85. package/src/client/app/renderers/P5Renderer.js +10 -7
  86. package/src/client/app/renderers/THREERenderer.js +136 -94
  87. package/src/client/app/stores/audioAnalysis.js +3 -4
  88. package/src/client/app/stores/console.js +9 -10
  89. package/src/client/app/stores/errors.js +1 -1
  90. package/src/client/app/stores/exports.js +36 -20
  91. package/src/client/app/stores/index.js +2 -2
  92. package/src/client/app/stores/layout.js +143 -138
  93. package/src/client/app/stores/multisampling.js +4 -4
  94. package/src/client/app/stores/props.js +76 -13
  95. package/src/client/app/stores/renderers.js +26 -15
  96. package/src/client/app/stores/rendering.js +108 -89
  97. package/src/client/app/stores/sketches.js +7 -9
  98. package/src/client/app/stores/time.js +18 -18
  99. package/src/client/app/stores/utils.js +95 -38
  100. package/src/client/app/transitions/fade.js +3 -3
  101. package/src/client/app/transitions/index.js +6 -7
  102. package/src/client/app/transitions/splitX.js +2 -2
  103. package/src/client/app/transitions/splitY.js +2 -2
  104. package/src/client/app/triggers/Keyboard.js +88 -79
  105. package/src/client/app/triggers/MIDI.js +110 -84
  106. package/src/client/app/triggers/Mouse.js +73 -65
  107. package/src/client/app/triggers/Trigger.js +59 -58
  108. package/src/client/app/triggers/index.js +7 -7
  109. package/src/client/app/triggers/shared.js +5 -5
  110. package/src/client/app/ui/Build.svelte +70 -71
  111. package/src/client/app/ui/ErrorOverlay.svelte +118 -104
  112. package/src/client/app/ui/Field.svelte +393 -258
  113. package/src/client/app/ui/FieldGroup.svelte +106 -94
  114. package/src/client/app/ui/FieldSection.svelte +127 -116
  115. package/src/client/app/ui/FieldSpace.svelte +29 -30
  116. package/src/client/app/ui/FieldTrigger.svelte +256 -244
  117. package/src/client/app/ui/FieldTriggers.svelte +46 -46
  118. package/src/client/app/ui/FloatingParams.svelte +29 -30
  119. package/src/client/app/ui/Layout.svelte +31 -32
  120. package/src/client/app/ui/LayoutColumn.svelte +4 -4
  121. package/src/client/app/ui/LayoutComponent.svelte +239 -225
  122. package/src/client/app/ui/LayoutResizer.svelte +195 -176
  123. package/src/client/app/ui/LayoutRoot.svelte +6 -6
  124. package/src/client/app/ui/LayoutRow.svelte +4 -4
  125. package/src/client/app/ui/LayoutToolbar.svelte +191 -194
  126. package/src/client/app/ui/Module.svelte +134 -135
  127. package/src/client/app/ui/ModuleHeaderAction.svelte +81 -78
  128. package/src/client/app/ui/ModuleHeaderButton.svelte +12 -12
  129. package/src/client/app/ui/ModuleHeaderSelect.svelte +47 -37
  130. package/src/client/app/ui/ModuleRenderer.svelte +26 -27
  131. package/src/client/app/ui/OutputRenderer.svelte +112 -105
  132. package/src/client/app/ui/ParamsMultisampling.svelte +96 -95
  133. package/src/client/app/ui/ParamsOutput.svelte +130 -113
  134. package/src/client/app/ui/Preview.svelte +7 -8
  135. package/src/client/app/ui/SelectChevrons.svelte +27 -15
  136. package/src/client/app/ui/SketchRenderer.svelte +780 -667
  137. package/src/client/app/ui/SketchSelect.svelte +50 -44
  138. package/src/client/app/ui/fields/ButtonInput.svelte +61 -48
  139. package/src/client/app/ui/fields/CheckboxInput.svelte +67 -61
  140. package/src/client/app/ui/fields/ColorInput.svelte +294 -238
  141. package/src/client/app/ui/fields/FieldInputRow.svelte +8 -8
  142. package/src/client/app/ui/fields/ImageInput.svelte +123 -121
  143. package/src/client/app/ui/fields/Input.svelte +100 -111
  144. package/src/client/app/ui/fields/IntervalInput.svelte +268 -0
  145. package/src/client/app/ui/fields/ListInput.svelte +96 -96
  146. package/src/client/app/ui/fields/NumberInput.svelte +120 -116
  147. package/src/client/app/ui/fields/ProgressInput.svelte +99 -73
  148. package/src/client/app/ui/fields/Select.svelte +137 -124
  149. package/src/client/app/ui/fields/TextInput.svelte +10 -11
  150. package/src/client/app/ui/fields/VectorInput.svelte +86 -82
  151. package/src/client/app/utils/canvas.utils.js +189 -208
  152. package/src/client/app/utils/color.utils.js +138 -101
  153. package/src/client/app/utils/fields.utils.js +131 -0
  154. package/src/client/app/utils/file.utils.js +209 -37
  155. package/src/client/app/utils/glsl.utils.js +2 -2
  156. package/src/client/app/utils/glslErrors.js +49 -31
  157. package/src/client/app/utils/index.js +32 -29
  158. package/src/client/app/utils/math.utils.js +14 -10
  159. package/src/client/index.html +16 -16
  160. package/src/client/main.js +4 -4
  161. package/src/client/public/css/global.css +26 -16
  162. package/src/cli/db.js +0 -17
  163. package/src/cli/index.js +0 -198
  164. package/src/cli/plugins/db.js +0 -12
  165. package/src/cli/plugins/hot-shader-reload.js +0 -86
  166. package/src/cli/plugins/screenshot.js +0 -46
  167. package/src/cli/server.js +0 -153
  168. package/src/cli/templates/2d.js +0 -15
  169. package/src/cli/templates/blank.js +0 -13
  170. package/src/cli/templates/fragment.js +0 -18
  171. package/src/cli/templates/index.js +0 -27
  172. package/src/cli/templates/p5.js +0 -13
  173. package/src/cli/templates/three-fragment.js +0 -53
  174. package/src/cli/templates/three-orthographic.js +0 -23
  175. package/src/cli/templates/three-perspective.js +0 -20
  176. package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +0 -56
  177. package/src/client/app/utils/props.utils.js +0 -51
  178. package/src/client/public/fonts/Inter-Bold.woff2 +0 -0
  179. package/src/client/public/fonts/Inter-Italic.woff2 +0 -0
  180. package/src/client/public/fonts/Inter-Regular.woff2 +0 -0
  181. package/src/client/public/fonts/Inter-SemiBold.woff2 +0 -0
  182. package/src/client/public/js/ffmpeg.min.js +0 -2
  183. package/src/client/public/js/ffmpeg.min.js.map +0 -1
  184. /package/src/cli/templates/{fragment.fs → fragment-gl/fragment.fs} +0 -0
package/.prettierignore CHANGED
@@ -1,5 +1,4 @@
1
- LICENSE.md
2
1
  pnpm-lock.yaml
3
2
  pnpm-workspace.yaml
4
- package.json
3
+ package-lock.json
5
4
  /docs
package/.prettierrc CHANGED
@@ -1,9 +1,25 @@
1
1
  {
2
- "semi": true,
3
- "tabWidth": 4,
4
- "useTabs": true,
5
- "singleQuote": true,
6
- "printWidth": 80,
7
- "bracketSpacing": true,
8
- "trailingComma": "all"
2
+ "semi": true,
3
+ "tabWidth": 4,
4
+ "useTabs": true,
5
+ "singleQuote": true,
6
+ "printWidth": 80,
7
+ "bracketSpacing": true,
8
+ "trailingComma": "all",
9
+ "plugins": ["prettier-plugin-svelte"],
10
+ "overrides": [
11
+ {
12
+ "files": ["README.md", "packages/*/README.md", "**/package.json"],
13
+ "options": {
14
+ "useTabs": false,
15
+ "tabWidth": 2
16
+ }
17
+ },
18
+ {
19
+ "files": ["docs/**/*.md"],
20
+ "options": {
21
+ "printWidth": 60
22
+ }
23
+ }
24
+ ]
9
25
  }
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <div align="center">A web development environment for creative coding</div>
3
3
  <br/>
4
4
 
5
- ![Screen capture of Fragment, splitted in two columns, the left one has a centered canvas displaying squares arranged in a grid, the right column contains various controls for colors, variables and exports](https://github.com/raphaelameaume/fragment/raw/main/screenshot.png "Screen Capture of Fragment")
5
+ ![Screen capture of Fragment, splitted in two columns, the left one has a centered canvas displaying squares arranged in a grid, the right column contains various controls for colors, variables and exports](https://github.com/raphaelameaume/fragment/raw/main/screenshot.png 'Screen Capture of Fragment')
6
6
 
7
7
  `fragment` provides a simple API to work with `<canvas>`.
8
8
 
@@ -12,17 +12,36 @@
12
12
  - Built-in GUI from sketch files
13
13
  - Export `<canvas>` to images (.png, .webm, .jpg) or videos (.mp4, .webm, .gif) on the fly
14
14
  - Hot shader reloading & [glslify](https://github.com/glslify/glslify) support
15
- - Interactive sketches using *triggers*
15
+ - Interactive sketches using _triggers_
16
16
  - Static build for production deployment
17
17
 
18
18
  ## Installation
19
19
 
20
+ ### With `npx`
21
+
22
+ ```bash
23
+ npx fragment-tools sketch.js --new
24
+ ```
25
+
26
+ ### Installing globally with `--global`
27
+
28
+ ```bash
29
+ npm install fragment-tools --global
20
30
  ```
21
- npm install fragment-tools -g
22
- ```
23
31
 
24
32
  You should now be able to run `fragment` from your command line.
25
33
 
34
+ ```bash
35
+ fragment sketch.js --new
36
+ ```
37
+
38
+ ### Installing locally
39
+
40
+ ```bash
41
+ npm install fragment-tools
42
+ npx fragment sketch.js --new
43
+ ```
44
+
26
45
  ## Usage
27
46
 
28
47
  ```
@@ -33,7 +52,7 @@ mkdir sketches
33
52
  cd sketches
34
53
 
35
54
  # create a sketch from a template
36
- fragment ./sketch.js --new --template=2d
55
+ npx fragment-tools ./sketch.js --new --template=2d
37
56
  ```
38
57
 
39
58
  Learn more about the available flag options in the [CLI docs](./docs/api/CLI.md).
@@ -48,9 +67,9 @@ export let props = {
48
67
  value: 10,
49
68
  params: {
50
69
  min: 4,
51
- max: 30
52
- }
53
- }
70
+ max: 30,
71
+ },
72
+ },
54
73
  };
55
74
 
56
75
  export let update = ({ context, width, height }) => {
@@ -80,7 +99,7 @@ Feel free to reach out on [Twitter](https://twitter.com/raphaelameaume) if you w
80
99
 
81
100
  ```
82
101
  # clone or fork the project
83
- git clone https://github.com/raphaelameaume/fragment.git
102
+ git clone https://github.com/raphaelameaume/fragment.git
84
103
 
85
104
  # move to the root of the repository
86
105
  cd fragment
package/bin/index.js CHANGED
@@ -1,20 +1,80 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import fs from 'node:fs';
3
4
  import sade from 'sade';
4
- import { run } from '../src/cli/index.js';
5
+ import { grey } from 'kleur/colors';
6
+ import { run } from '../src/cli/run.js';
7
+ import { build } from '../src/cli/build.js';
8
+ import { create } from '../src/cli/create.js';
9
+ import { preview } from '../src/cli/preview.js';
5
10
 
6
- sade('fragment [entry]')
7
- .version('0.1.13')
11
+ const { version } = JSON.parse(
12
+ fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'),
13
+ );
14
+
15
+ console.log(`${grey(`[fragment] v${version}`)}\n`);
16
+
17
+ const prog = sade('fragment');
18
+ prog.version(`${version}`);
19
+
20
+ prog.command('run [entry]', '', { default: true })
8
21
  .describe('Run a dev environment for fragment')
9
- .option('-n, --new', 'Create file if it does not exist', false)
22
+ .option('-n, --new', 'Create a new sketch', false)
10
23
  .option('-t, --template', 'Specify template to create the file from', '2d')
11
24
  .option('-p, --port', 'Port to bind', 3000)
12
25
  .option('-dev, --development', 'Enable development mode', false)
13
26
  .option('-b, --build', 'Build sketch for production', false)
14
- .option('--exportDir', 'Directory used for exports', process.cwd())
15
- .option('--outDir', 'Directory used for static build', null)
16
- .option('--emptyOutDir', 'Empty outDir before static build', false)
27
+ .option('--exportDir', 'Directory used for exports')
28
+ .option('--outDir', 'Build output directory')
29
+ .option('--emptyOutDir', 'Empty outDir before static build')
30
+ .option('--base', 'Base public path when served in production', undefined)
31
+ .action((entry, options) => {
32
+ if (options.new) {
33
+ return create(entry, {
34
+ templateName: options.template,
35
+ });
36
+ }
37
+
38
+ if (options.build) {
39
+ return build(entry, {
40
+ development: options.development,
41
+ outDir: options.outDir,
42
+ emptyOutDir: options.emptyOutDir,
43
+ base: options.base,
44
+ });
45
+ }
46
+
47
+ run(entry, {
48
+ development: options.development,
49
+ exportDir: options.exportDir,
50
+ port: options.port,
51
+ });
52
+ });
53
+
54
+ prog.command('create [entry]')
55
+ .describe('Create a new sketch')
56
+ .option('-t, --template', 'Specify template to create the file from', '2d')
57
+ .action((entry = '', options) => {
58
+ create(entry, {
59
+ templateName: options.template,
60
+ });
61
+ });
62
+
63
+ prog.command('build [entry]')
64
+ .describe('Build a sketch')
65
+ .option('--outDir', 'Output folder', null)
66
+ .option('--emptyOutDir', 'Empty outDir before building for production')
67
+ .option('--base', 'Base public path', undefined)
68
+ .option('-dev, --development', 'Enable development mode', false)
17
69
  .action((entry, options) => {
18
- run(entry, options);
19
- })
20
- .parse(process.argv);
70
+ build(entry, options);
71
+ });
72
+
73
+ prog.command('preview [directory]')
74
+ .describe('Preview a sketch')
75
+ .option('-o, --open', 'Open in browser', false)
76
+ .action((dir, options) => {
77
+ preview(dir, options);
78
+ });
79
+
80
+ prog.parse(process.argv);
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "fragment-tools",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "A web development environment for creative coding",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "fragment": "bin/index.js"
8
8
  },
9
+ "scripts": {
10
+ "format": "prettier . --write"
11
+ },
9
12
  "type": "module",
10
13
  "repository": {
11
14
  "type": "git",
@@ -18,8 +21,8 @@
18
21
  },
19
22
  "homepage": "https://github.com/raphaelameaume/fragment#readme",
20
23
  "dependencies": {
21
- "@rollup/plugin-alias": "^3.1.5",
22
- "@sveltejs/vite-plugin-svelte": "^1.0.2",
24
+ "@clack/core": "^0.3.3",
25
+ "@sveltejs/vite-plugin-svelte": "^2.4.5",
23
26
  "body-parser": "^1.20.0",
24
27
  "changedpi": "^1.0.4",
25
28
  "chokidar": "^3.5.2",
@@ -27,12 +30,17 @@
27
30
  "get-port": "^6.0.0",
28
31
  "gifenc": "^1.0.3",
29
32
  "glslify": "^7.1.1",
33
+ "is-unicode-supported": "^2.0.0",
30
34
  "kleur": "^4.1.4",
31
35
  "sade": "^1.7.4",
32
- "svelte": "^3.49.0",
33
- "svelte-json-tree": "^1.0.0",
34
- "vite": "^3.0.9",
36
+ "svelte": "^4.0.0",
37
+ "svelte-json-tree": "^2.2.0",
38
+ "vite": "^4.4.9",
35
39
  "webm-writer": "^1.0.0",
36
40
  "ws": "^8.2.3"
41
+ },
42
+ "devDependencies": {
43
+ "prettier": "^3.2.5",
44
+ "prettier-plugin-svelte": "^3.1.2"
37
45
  }
38
46
  }
@@ -0,0 +1,125 @@
1
+ import path from 'node:path';
2
+ import { readdir } from 'node:fs/promises';
3
+ import { mergeConfig, build as viteBuild } from 'vite';
4
+ import { createFragmentFile } from './createFragmentFile.js';
5
+ import { createConfig } from './createConfig.js';
6
+ import { getEntries } from './getEntries.js';
7
+ import { log, magenta } from './log.js';
8
+ import * as p from './prompts.js';
9
+ import { handleCancelledPrompt, mkdirp, prettifyTime } from './utils.js';
10
+ import hotShaderReplacement from './plugins/hot-shader-replacement.js';
11
+
12
+ /**
13
+ * Build a sketch for production
14
+ * @param {string} entry
15
+ * @param {object} options
16
+ * @param {string} options.base
17
+ * @param {string} options.outDir
18
+ * @param {boolean} options.emptyOutDir
19
+ * @param {boolean} options.development
20
+ */
21
+ export async function build(entry, options) {
22
+ const cwd = process.cwd();
23
+ const command = 'build';
24
+ const prefix = log.prefix(command);
25
+
26
+ try {
27
+ const entries = await getEntries(entry, cwd, command, prefix);
28
+
29
+ if (!entries.length) return;
30
+
31
+ log.message(`${magenta(entry)}\n`, prefix);
32
+
33
+ const outDir = await p.text({
34
+ message: 'Output directory:',
35
+ placeholder: '.',
36
+ hint: '(hit Enter to use current directory)',
37
+ initialValue:
38
+ options.outDir ?? entries[0].split(path.extname(entries[0]))[0],
39
+ });
40
+
41
+ handleCancelledPrompt(outDir, prefix);
42
+
43
+ let outDirPath = path.join(cwd, outDir);
44
+
45
+ // create directory if it doesn't exist
46
+ mkdirp(outDirPath);
47
+
48
+ const files = await readdir(outDirPath);
49
+
50
+ let emptyOutDir = options.emptyOutDir ?? true;
51
+
52
+ if (files.length > 0) {
53
+ log.warn(`${outDirPath} is not an empty folder.\n`);
54
+
55
+ emptyOutDir = await p.confirm({
56
+ message: 'Empty folder before building?',
57
+ active: 'Yes',
58
+ inactive: 'No',
59
+ initialValue: emptyOutDir ?? true,
60
+ });
61
+
62
+ handleCancelledPrompt(emptyOutDir, prefix);
63
+ }
64
+
65
+ const base = await p.text({
66
+ message: 'Base public path:',
67
+ placeholder: `/`,
68
+ hint: '(Hit Enter to validate)',
69
+ initialValue: options.base,
70
+ });
71
+
72
+ handleCancelledPrompt(base, prefix);
73
+
74
+ if (entries.length > 0) {
75
+ log.message(
76
+ `Building ${magenta(entries[0])} for production...\n`,
77
+ prefix,
78
+ );
79
+
80
+ const fragmentFilepath = await createFragmentFile(entries, cwd);
81
+ const config = createConfig(
82
+ entries,
83
+ fragmentFilepath,
84
+ {
85
+ dev: options.development,
86
+ build: true,
87
+ },
88
+ cwd,
89
+ );
90
+
91
+ if (entries.length > 1) {
92
+ log.error(
93
+ `fragment can only build one sketch at a time.`,
94
+ prefix,
95
+ );
96
+ return;
97
+ }
98
+
99
+ let startTime = Date.now();
100
+
101
+ await viteBuild(
102
+ mergeConfig(config, {
103
+ logLevel: 'info',
104
+ base,
105
+ build: {
106
+ outDir: outDirPath,
107
+ emptyOutDir,
108
+ },
109
+ plugins: [hotShaderReplacement({ cwd })],
110
+ }),
111
+ );
112
+
113
+ // line break after vite logs
114
+ log.message();
115
+
116
+ log.success(
117
+ `Done in ${prettifyTime(Date.now() - startTime)}`,
118
+ prefix,
119
+ );
120
+ }
121
+ } catch (error) {
122
+ log.error(`Error\n`, prefix);
123
+ console.error(error);
124
+ }
125
+ }
@@ -0,0 +1,238 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { readdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { log, magenta, bold, cyan, dim } from './log.js';
5
+ import * as p from './prompts.js';
6
+ import {
7
+ packageManager,
8
+ file,
9
+ mkdirp,
10
+ handleCancelledPrompt,
11
+ prettifyTime,
12
+ addExtension,
13
+ } from './utils.js';
14
+
15
+ /**
16
+ * Create a new sketch
17
+ * @param {string} entry
18
+ * @param {object} options
19
+ * @param {string} options.templateName
20
+ */
21
+ export async function create(entry, { templateName } = {}) {
22
+ const cwd = process.cwd();
23
+ const prefix = log.prefix('create');
24
+
25
+ try {
26
+ log.message(`${magenta(entry)}\n`, prefix);
27
+
28
+ let dir, name;
29
+
30
+ if (entry) {
31
+ const { dir: entryDir, base: entryBase } = path.parse(entry);
32
+
33
+ dir = entryDir;
34
+ name = entryBase;
35
+ }
36
+
37
+ dir = await p.text({
38
+ message: 'Specify an output directory:',
39
+ placeholder: '.',
40
+ hint: '(hit Enter to use current directory)',
41
+ initialValue: dir,
42
+ });
43
+
44
+ handleCancelledPrompt(dir, prefix);
45
+
46
+ if (!dir) {
47
+ dir = cwd;
48
+ }
49
+
50
+ name = await p.text({
51
+ message: 'Specify a sketch name:',
52
+ placeholder: ` `,
53
+ hint: '(hit Enter to validate)',
54
+ initialValue: name,
55
+ validate: (value) => {
56
+ if (value.length === 0) return `A name is required.`;
57
+ },
58
+ });
59
+
60
+ handleCancelledPrompt(name, prefix);
61
+
62
+ name = name.replace(/\s/g, '-');
63
+
64
+ let templatesOptions = fs
65
+ .readdirSync(file('./templates'))
66
+ .map((dir) => {
67
+ const metadata = file(`./templates/${dir}/meta.json`);
68
+ const template = JSON.parse(fs.readFileSync(metadata, 'utf8'));
69
+
70
+ return {
71
+ ...template,
72
+ path: dir,
73
+ // for prompts
74
+ label: template.name,
75
+ hint: template.description,
76
+ value: template.name,
77
+ };
78
+ });
79
+
80
+ // put default template in the first place
81
+ templatesOptions = templatesOptions.sort((a, b) =>
82
+ a.isDefault ? -1 : 1,
83
+ );
84
+
85
+ templateName = await p.select({
86
+ message: 'Pick a template:',
87
+ options: templatesOptions,
88
+ initialValue: templatesOptions.find(
89
+ (option) => templateName === option.name,
90
+ )?.value,
91
+ });
92
+
93
+ handleCancelledPrompt(templateName, prefix);
94
+
95
+ let template = templatesOptions.find(
96
+ (option) => option.value === templateName,
97
+ );
98
+
99
+ const hasDependencies = template.dependencies?.length > 0;
100
+ const singleDependency = template.dependencies?.length === 1;
101
+
102
+ if (hasDependencies) {
103
+ const dependenciesList = template.dependencies
104
+ .map((dependency) => bold(`${dependency}`))
105
+ .join(',');
106
+
107
+ log.warn(
108
+ `This template has the following ${singleDependency ? 'dependency' : 'dependencies'}: ${dependenciesList}. ${singleDependency ? 'It' : 'They'} need${singleDependency ? 's' : ''} to be installed before running Fragment.\n`,
109
+ );
110
+ }
111
+
112
+ mkdirp(dir);
113
+
114
+ const from = file(`./templates/${template.path}`);
115
+ const to = dir;
116
+
117
+ let filename = path.basename(name).split('.')[0];
118
+ let excludes = ['meta.json'];
119
+ let templateFiles = await readdir(from);
120
+
121
+ templateFiles = templateFiles.filter(
122
+ (file) => !excludes.includes(file),
123
+ );
124
+
125
+ let startTime = Date.now();
126
+ log.message(
127
+ `Creating sketch from template ${magenta(template.name)}...\n`,
128
+ prefix,
129
+ );
130
+
131
+ const checkForFileExistence = async (filepath) => {
132
+ if (fs.existsSync(filepath)) {
133
+ const dirname = path.dirname(filepath);
134
+ const ext = path.extname(filepath);
135
+
136
+ let filename = path.basename(filepath);
137
+
138
+ log.warn(`${filename} already exists in ${dirname}.\n`);
139
+
140
+ let override = await p.confirm({
141
+ message: `Override ${filename}?`,
142
+ active: 'Yes',
143
+ inactive: 'No',
144
+ initialValue: false,
145
+ });
146
+
147
+ handleCancelledPrompt(override, prefix);
148
+
149
+ if (!override) {
150
+ filename = await p.text({
151
+ message: 'Pick a different name:',
152
+ placeholder: ` `,
153
+ hint: '(hit Enter to validate)',
154
+ initialValue: filename,
155
+ validate: (value) => {
156
+ if (value.length === 0)
157
+ return `A name is required.`;
158
+ },
159
+ });
160
+
161
+ handleCancelledPrompt(filename, prefix);
162
+
163
+ filename = filename.replace(/\s/g, '-');
164
+
165
+ return await checkForFileExistence(
166
+ path.join(dirname, addExtension(filename, ext)),
167
+ );
168
+ }
169
+
170
+ return filepath;
171
+ }
172
+
173
+ return filepath;
174
+ };
175
+
176
+ const newFiles = [];
177
+
178
+ for (let i = 0; i < templateFiles.length; i++) {
179
+ const file = templateFiles[i];
180
+ const source = path.join(from, file);
181
+ const ext = path.extname(source);
182
+
183
+ let dest = path.join(to, `${filename}${ext}`);
184
+
185
+ log.info(
186
+ `Copying templates/${template.path}/${file} to ${path.relative(cwd, dest)}...`,
187
+ );
188
+
189
+ dest = await checkForFileExistence(dest);
190
+
191
+ let buffer = await readFile(source);
192
+ let content = buffer.toString();
193
+
194
+ // replace references to template files by new files paths
195
+ templateFiles.forEach((templateFile, index) => {
196
+ if (index === i) return;
197
+
198
+ if (newFiles.length > 0) {
199
+ const newFile = newFiles[index];
200
+
201
+ content = content.replace(
202
+ new RegExp(templateFile, 'g'),
203
+ `${path.basename(newFile)}`,
204
+ );
205
+ }
206
+ });
207
+
208
+ await writeFile(dest, Buffer.from(content));
209
+
210
+ log.success(
211
+ `Copied templates/${template.path}/${file} to ${path.relative(cwd, dest)}\n`,
212
+ );
213
+
214
+ newFiles.push(dest);
215
+ }
216
+
217
+ log.success(
218
+ `Done in ${prettifyTime(Date.now() - startTime)}\n`,
219
+ prefix,
220
+ );
221
+ log.info(`${newFiles.join('\n')}\n`);
222
+
223
+ let i = 1;
224
+
225
+ let nextSteps = ``;
226
+
227
+ if (hasDependencies) {
228
+ nextSteps += `${dim(`${i++}. Install dependencies`)}\n${bold(cyan(`${packageManager} install ${template.dependencies.join(' ')}`))}\n\n`;
229
+ }
230
+
231
+ nextSteps += `${dim(`${i++}. Start Fragment`)}\n${bold(cyan(`fragment ${path.relative(cwd, newFiles[newFiles.length - 1])}`))}`;
232
+
233
+ p.note(nextSteps, 'Next steps');
234
+ } catch (error) {
235
+ log.error(`Error\n`, prefix);
236
+ console.error(error);
237
+ }
238
+ }
@@ -0,0 +1,82 @@
1
+ import path from 'node:path';
2
+ import { defineConfig } from 'vite';
3
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
4
+
5
+ import checkDependencies from './plugins/check-dependencies.js';
6
+ import { file } from './utils.js';
7
+ import { log } from './log.js';
8
+
9
+ /**
10
+ * Create Vite config from entries
11
+ * @param {string[]} entries
12
+ * @param {string} fragmentFilepath
13
+ * @param {options} options
14
+ * @param {boolean} [options.dev=false]
15
+ * @param {boolean} [options.build=false]
16
+ * @param {string} [cwd=process.cwd()]
17
+ * @returns {import('vite').UserConfig}
18
+ */
19
+ export function createConfig(
20
+ entries,
21
+ fragmentFilepath,
22
+ { dev = false, build = false } = {},
23
+ cwd = process.cwd(),
24
+ ) {
25
+ const entriesPaths = entries.map((entry) => path.join(cwd, entry));
26
+ const root = file('../client');
27
+ const app = path.join(root, 'app');
28
+
29
+ log.info(`Creating Vite configuration...`);
30
+
31
+ return defineConfig({
32
+ configFile: false,
33
+ root,
34
+ logLevel: dev ? 'info' : 'silent',
35
+ resolve: {
36
+ alias: [
37
+ { find: '@fragment/sketches', replacement: fragmentFilepath },
38
+ { find: '@fragment', replacement: app },
39
+ {
40
+ find: 'three',
41
+ replacement: path.join(cwd, 'node_modules/three'),
42
+ },
43
+ { find: 'p5', replacement: path.join(cwd, 'node_modules/p5') },
44
+ {
45
+ find: 'ogl',
46
+ replacement: path.join(cwd, 'node_modules/ogl'),
47
+ },
48
+ ],
49
+ },
50
+ plugins: [
51
+ svelte({
52
+ configFile: false,
53
+ onwarn: (warning, handler) => {
54
+ if (dev) {
55
+ handler(warning);
56
+ } else {
57
+ return;
58
+ }
59
+ },
60
+ }),
61
+ checkDependencies({
62
+ cwd,
63
+ app,
64
+ entriesPaths,
65
+ build,
66
+ }),
67
+ ],
68
+
69
+ define: {
70
+ __CWD__: `${JSON.stringify(cwd)}`,
71
+ __FRAGMENT_PORT__: undefined,
72
+ __START_TIME__: Date.now(),
73
+ __SEED__: Date.now(),
74
+ __BUILD__: build,
75
+ __DEV__: !build,
76
+ },
77
+ optimizeDeps: {
78
+ include: ['convert-length', 'webm-writer', 'changedpi'],
79
+ exclude: ['@fragment/sketches', ...entriesPaths],
80
+ },
81
+ });
82
+ }