fragment-tools 0.1.14 → 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 (156) hide show
  1. package/.prettierignore +1 -2
  2. package/.prettierrc +23 -7
  3. package/README.md +28 -9
  4. package/bin/index.js +69 -9
  5. package/package.json +11 -2
  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 +38 -36
  12. package/src/cli/plugins/check-dependencies.js +52 -23
  13. package/src/cli/plugins/hot-shader-replacement.js +63 -39
  14. package/src/cli/plugins/hot-sketch-reload.js +21 -15
  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/components/IconCross.svelte +18 -18
  40. package/src/client/app/components/Init.svelte +30 -1
  41. package/src/client/app/components/KeyBinding.svelte +22 -22
  42. package/src/client/app/inputs/Input.js +9 -9
  43. package/src/client/app/inputs/Mouse.js +1 -1
  44. package/src/client/app/inputs/Webcam.js +89 -88
  45. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +7 -6
  46. package/src/client/app/lib/canvas-recorder/H264Recorder.js +45 -0
  47. package/src/client/app/lib/canvas-recorder/MP4Recorder.js +7 -9
  48. package/src/client/app/lib/canvas-recorder/WebMRecorder.js +3 -4
  49. package/src/client/app/lib/canvas-recorder/mp4.js +1649 -15
  50. package/src/client/app/lib/canvas-recorder/utils.js +33 -17
  51. package/src/client/app/lib/gl/Geometry.js +11 -8
  52. package/src/client/app/lib/gl/Program.js +38 -19
  53. package/src/client/app/lib/gl/Renderer.js +124 -105
  54. package/src/client/app/lib/gl/Texture.js +113 -85
  55. package/src/client/app/lib/gl/index.js +12 -12
  56. package/src/client/app/lib/gl/utils.js +1 -3
  57. package/src/client/app/lib/helpers/frameDebounce.js +30 -30
  58. package/src/client/app/lib/loader/index.js +10 -10
  59. package/src/client/app/lib/loader/loadImage.js +15 -15
  60. package/src/client/app/lib/loader/loadScript.js +1 -1
  61. package/src/client/app/lib/paper-sizes.js +75 -76
  62. package/src/client/app/lib/presets.js +25 -5
  63. package/src/client/app/lib/tempo/Analyser.js +18 -17
  64. package/src/client/app/lib/tempo/Range.js +15 -12
  65. package/src/client/app/lib/tempo/index.js +34 -27
  66. package/src/client/app/modules/AudioAnalyser/Range.svelte +69 -72
  67. package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +20 -19
  68. package/src/client/app/modules/AudioAnalyser.svelte +52 -35
  69. package/src/client/app/modules/Console/ConsoleLine.svelte +193 -172
  70. package/src/client/app/modules/Console.svelte +76 -74
  71. package/src/client/app/modules/Monitor.svelte +57 -57
  72. package/src/client/app/modules/Params.svelte +17 -5
  73. package/src/client/app/renderers/P5GLRenderer.js +144 -0
  74. package/src/client/app/stores/audioAnalysis.js +3 -4
  75. package/src/client/app/stores/console.js +9 -10
  76. package/src/client/app/stores/errors.js +1 -1
  77. package/src/client/app/stores/index.js +2 -2
  78. package/src/client/app/stores/layout.js +143 -138
  79. package/src/client/app/stores/multisampling.js +4 -4
  80. package/src/client/app/stores/props.js +52 -12
  81. package/src/client/app/stores/renderers.js +4 -0
  82. package/src/client/app/stores/rendering.js +108 -89
  83. package/src/client/app/stores/time.js +18 -18
  84. package/src/client/app/transitions/fade.js +3 -3
  85. package/src/client/app/transitions/index.js +6 -7
  86. package/src/client/app/transitions/splitX.js +2 -2
  87. package/src/client/app/transitions/splitY.js +2 -2
  88. package/src/client/app/triggers/Mouse.js +73 -65
  89. package/src/client/app/triggers/Trigger.js +59 -58
  90. package/src/client/app/triggers/index.js +7 -7
  91. package/src/client/app/triggers/shared.js +5 -5
  92. package/src/client/app/ui/Build.svelte +70 -71
  93. package/src/client/app/ui/ErrorOverlay.svelte +118 -104
  94. package/src/client/app/ui/Field.svelte +58 -26
  95. package/src/client/app/ui/FieldSection.svelte +5 -3
  96. package/src/client/app/ui/FieldSpace.svelte +29 -30
  97. package/src/client/app/ui/FieldTrigger.svelte +256 -244
  98. package/src/client/app/ui/FieldTriggers.svelte +46 -46
  99. package/src/client/app/ui/FloatingParams.svelte +29 -30
  100. package/src/client/app/ui/Layout.svelte +31 -32
  101. package/src/client/app/ui/LayoutColumn.svelte +4 -4
  102. package/src/client/app/ui/LayoutComponent.svelte +239 -225
  103. package/src/client/app/ui/LayoutResizer.svelte +195 -176
  104. package/src/client/app/ui/LayoutRoot.svelte +6 -6
  105. package/src/client/app/ui/LayoutRow.svelte +4 -4
  106. package/src/client/app/ui/LayoutToolbar.svelte +191 -194
  107. package/src/client/app/ui/Module.svelte +134 -135
  108. package/src/client/app/ui/ModuleHeaderAction.svelte +81 -78
  109. package/src/client/app/ui/ModuleHeaderButton.svelte +12 -12
  110. package/src/client/app/ui/ModuleHeaderSelect.svelte +47 -37
  111. package/src/client/app/ui/ModuleRenderer.svelte +26 -27
  112. package/src/client/app/ui/OutputRenderer.svelte +112 -105
  113. package/src/client/app/ui/ParamsOutput.svelte +28 -11
  114. package/src/client/app/ui/Preview.svelte +7 -8
  115. package/src/client/app/ui/SketchRenderer.svelte +35 -16
  116. package/src/client/app/ui/SketchSelect.svelte +50 -44
  117. package/src/client/app/ui/fields/CheckboxInput.svelte +1 -1
  118. package/src/client/app/ui/fields/FieldInputRow.svelte +8 -8
  119. package/src/client/app/ui/fields/IntervalInput.svelte +268 -0
  120. package/src/client/app/ui/fields/NumberInput.svelte +10 -11
  121. package/src/client/app/ui/fields/ProgressInput.svelte +29 -10
  122. package/src/client/app/ui/fields/TextInput.svelte +10 -11
  123. package/src/client/app/ui/fields/VectorInput.svelte +2 -2
  124. package/src/client/app/utils/canvas.utils.js +46 -92
  125. package/src/client/app/utils/color.utils.js +138 -101
  126. package/src/client/app/utils/fields.utils.js +131 -0
  127. package/src/client/app/utils/file.utils.js +169 -1
  128. package/src/client/app/utils/glsl.utils.js +2 -2
  129. package/src/client/app/utils/glslErrors.js +49 -31
  130. package/src/client/app/utils/index.js +32 -29
  131. package/src/client/app/utils/math.utils.js +14 -10
  132. package/src/client/index.html +16 -16
  133. package/src/client/main.js +4 -4
  134. package/src/client/public/css/global.css +6 -2
  135. package/src/cli/db.js +0 -17
  136. package/src/cli/index.js +0 -198
  137. package/src/cli/plugins/db.js +0 -12
  138. package/src/cli/plugins/screenshot.js +0 -83
  139. package/src/cli/server.js +0 -164
  140. package/src/cli/templates/2d.js +0 -15
  141. package/src/cli/templates/blank.js +0 -13
  142. package/src/cli/templates/fragment.js +0 -18
  143. package/src/cli/templates/index.js +0 -27
  144. package/src/cli/templates/p5.js +0 -13
  145. package/src/cli/templates/three-fragment.js +0 -53
  146. package/src/cli/templates/three-orthographic.js +0 -23
  147. package/src/cli/templates/three-perspective.js +0 -20
  148. package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +0 -56
  149. package/src/client/app/utils/props.utils.js +0 -51
  150. package/src/client/public/fonts/Inter-Bold.woff2 +0 -0
  151. package/src/client/public/fonts/Inter-Italic.woff2 +0 -0
  152. package/src/client/public/fonts/Inter-Regular.woff2 +0 -0
  153. package/src/client/public/fonts/Inter-SemiBold.woff2 +0 -0
  154. package/src/client/public/js/ffmpeg.min.js +0 -2
  155. package/src/client/public/js/ffmpeg.min.js.map +0 -1
  156. /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.14')
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
27
  .option('--exportDir', 'Directory used for exports')
15
- .option('--outDir', 'Directory used for static build', null)
16
- .option('--emptyOutDir', 'Empty outDir before static build', false)
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.14",
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,6 +21,7 @@
18
21
  },
19
22
  "homepage": "https://github.com/raphaelameaume/fragment#readme",
20
23
  "dependencies": {
24
+ "@clack/core": "^0.3.3",
21
25
  "@sveltejs/vite-plugin-svelte": "^2.4.5",
22
26
  "body-parser": "^1.20.0",
23
27
  "changedpi": "^1.0.4",
@@ -26,12 +30,17 @@
26
30
  "get-port": "^6.0.0",
27
31
  "gifenc": "^1.0.3",
28
32
  "glslify": "^7.1.1",
33
+ "is-unicode-supported": "^2.0.0",
29
34
  "kleur": "^4.1.4",
30
35
  "sade": "^1.7.4",
31
36
  "svelte": "^4.0.0",
32
- "svelte-json-tree": "^1.0.0",
37
+ "svelte-json-tree": "^2.2.0",
33
38
  "vite": "^4.4.9",
34
39
  "webm-writer": "^1.0.0",
35
40
  "ws": "^8.2.3"
41
+ },
42
+ "devDependencies": {
43
+ "prettier": "^3.2.5",
44
+ "prettier-plugin-svelte": "^3.1.2"
36
45
  }
37
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
+ }