fragment-tools 0.2.11 → 0.2.13
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 +12 -11
- package/src/cli/build.js +1 -0
- package/src/cli/create.js +22 -4
- package/src/cli/createConfig.js +2 -2
- package/src/cli/getEntries.js +10 -1
- package/src/cli/plugins/hot-shader-replacement.js +54 -16
- package/src/cli/plugins/save.js +97 -38
- package/src/cli/prompts.js +89 -36
- package/src/cli/run.js +1 -1
- package/src/cli/templates/blank/index.ts +1 -1
- package/src/cli/templates/default/index.js +10 -2
- package/src/cli/templates/default/index.ts +5 -2
- package/src/cli/templates/fragment-gl/index.ts +1 -1
- package/src/cli/templates/p5/index.ts +1 -1
- package/src/cli/templates/p5-webgl/index.ts +1 -1
- package/src/cli/templates/three-fragment/index.js +5 -3
- package/src/cli/templates/three-fragment/index.ts +5 -4
- package/src/cli/templates/three-orthographic/index.js +6 -1
- package/src/cli/templates/three-orthographic/index.ts +6 -2
- package/src/cli/templates/three-perspective/index.js +6 -1
- package/src/cli/templates/three-perspective/index.ts +6 -2
- package/src/client/app/actions/resize.js +8 -1
- package/src/client/app/attachments/draggable.js +93 -0
- package/src/client/app/client.js +90 -18
- package/src/client/app/components/IconFlip.svelte +46 -0
- package/src/client/app/hooks.js +25 -1
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
- package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
- package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
- package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
- package/src/client/app/lib/canvas-recorder/utils.js +18 -9
- package/src/client/app/modules/Params.svelte +1 -0
- package/src/client/app/renderers/2DRenderer.js +20 -16
- package/src/client/app/renderers/P5GLRenderer.js +13 -5
- package/src/client/app/renderers/P5Renderer.js +9 -1
- package/src/client/app/renderers/THREERenderer.js +63 -48
- package/src/client/app/state/Sketch.svelte.js +150 -10
- package/src/client/app/state/errors.svelte.js +19 -0
- package/src/client/app/state/exports.svelte.js +14 -1
- package/src/client/app/state/rendering.svelte.js +90 -13
- package/src/client/app/state/sketches.svelte.js +43 -7
- package/src/client/app/state/utils.svelte.js +49 -0
- package/src/client/app/ui/Field.svelte +63 -16
- package/src/client/app/ui/FieldSection.svelte +4 -4
- package/src/client/app/ui/ParamsOutput.svelte +7 -5
- package/src/client/app/ui/SketchRenderer.svelte +21 -0
- package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
- package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
- package/src/client/app/ui/fields/ColorInput.svelte +16 -11
- package/src/client/app/ui/fields/GradientInput.svelte +607 -0
- package/src/client/app/ui/fields/Input.svelte +10 -6
- package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
- package/src/client/app/ui/fields/NumberInput.svelte +51 -13
- package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
- package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
- package/src/client/app/ui/fields/TextareaInput.svelte +93 -0
- package/src/client/app/utils/canvas.utils.js +105 -28
- package/src/client/app/utils/color.utils.js +74 -17
- package/src/client/app/utils/fields.utils.js +70 -17
- package/src/client/app/utils/file.utils.js +86 -31
- package/src/client/app/utils/glsl.utils.js +11 -2
- package/src/client/app/utils/glslErrors.js +31 -21
- package/src/client/app/utils/index.js +28 -12
- package/src/client/main.js +7 -1
- package/src/types/global.d.ts +143 -0
- package/src/types/props.d.ts +41 -15
- package/tsconfig.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fragment-tools",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "A web development environment for creative coding",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,24 +24,25 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/raphaelameaume/fragment#readme",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@clack/core": "^
|
|
28
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
29
|
-
"changedpi": "
|
|
30
|
-
"convert-length": "
|
|
27
|
+
"@clack/core": "^1.3.1",
|
|
28
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
29
|
+
"changedpi": "1.0.4",
|
|
30
|
+
"convert-length": "1.0.1",
|
|
31
31
|
"get-port": "^7.1.0",
|
|
32
|
-
"gifenc": "
|
|
32
|
+
"gifenc": "1.0.3",
|
|
33
33
|
"is-unicode-supported": "^2.0.0",
|
|
34
|
-
"kleur": "^4.1.
|
|
34
|
+
"kleur": "^4.1.5",
|
|
35
35
|
"mediabunny": "^1.13.3",
|
|
36
36
|
"milliparsec": "^5.1.0",
|
|
37
|
-
"sade": "^1.
|
|
38
|
-
"svelte": "^5.
|
|
39
|
-
"vite": "^
|
|
40
|
-
"ws": "^8.
|
|
37
|
+
"sade": "^1.8.1",
|
|
38
|
+
"svelte": "^5.55.9",
|
|
39
|
+
"vite": "^8.0.14",
|
|
40
|
+
"ws": "^8.20.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@changesets/cli": "^2.29.7",
|
|
44
44
|
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
|
|
45
|
+
"@types/node": "^25.9.1",
|
|
45
46
|
"@types/p5": "^1.7.6",
|
|
46
47
|
"@types/three": "^0.174.0",
|
|
47
48
|
"prettier": "^3.2.5",
|
package/src/cli/build.js
CHANGED
package/src/cli/create.js
CHANGED
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
* Create a new sketch
|
|
19
19
|
* @param {string} entry
|
|
20
20
|
* @param {object} options
|
|
21
|
-
* @param {string} options.templateName
|
|
22
|
-
* @param {boolean} options.typescript
|
|
21
|
+
* @param {string} [options.templateName]
|
|
22
|
+
* @param {boolean} [options.typescript]
|
|
23
23
|
*/
|
|
24
24
|
export async function create(entry, { templateName, typescript } = {}) {
|
|
25
25
|
const cwd = process.cwd();
|
|
@@ -28,7 +28,10 @@ export async function create(entry, { templateName, typescript } = {}) {
|
|
|
28
28
|
try {
|
|
29
29
|
log.message(`${magenta(entry)}\n`, prefix);
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
/** @type {string |undefined} */
|
|
32
|
+
let dir;
|
|
33
|
+
/** @type {string |undefined} */
|
|
34
|
+
let name;
|
|
32
35
|
|
|
33
36
|
if (entry) {
|
|
34
37
|
const { dir: entryDir, base: entryBase } = path.parse(entry);
|
|
@@ -42,6 +45,7 @@ export async function create(entry, { templateName, typescript } = {}) {
|
|
|
42
45
|
placeholder: '.',
|
|
43
46
|
hint: '(hit Enter to use current directory)',
|
|
44
47
|
initialValue: dir,
|
|
48
|
+
defaultValue: '.',
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
handleCancelledPrompt(dir, prefix);
|
|
@@ -56,7 +60,9 @@ export async function create(entry, { templateName, typescript } = {}) {
|
|
|
56
60
|
hint: '(hit Enter to validate)',
|
|
57
61
|
initialValue: name,
|
|
58
62
|
validate: (value) => {
|
|
59
|
-
if (value.length === 0) return `A name is required.`;
|
|
63
|
+
if (!value || value.length === 0) return `A name is required.`;
|
|
64
|
+
|
|
65
|
+
return undefined;
|
|
60
66
|
},
|
|
61
67
|
});
|
|
62
68
|
|
|
@@ -64,6 +70,18 @@ export async function create(entry, { templateName, typescript } = {}) {
|
|
|
64
70
|
|
|
65
71
|
name = name.replace(/\s/g, '-');
|
|
66
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @typedef TemplateOption
|
|
75
|
+
* @property {string} name
|
|
76
|
+
* @property {string} description
|
|
77
|
+
* @property {string} path
|
|
78
|
+
* @property {string} label
|
|
79
|
+
* @property {string} hint
|
|
80
|
+
* @property {string} value
|
|
81
|
+
* @property {boolean} [isDefault=false]
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/** @type {TemplateOption[]} */
|
|
67
85
|
let templatesOptions = fs
|
|
68
86
|
.readdirSync(file('./templates'))
|
|
69
87
|
.map((dir) => {
|
package/src/cli/createConfig.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import url from 'node:url';
|
|
4
|
-
import { defineConfig,
|
|
4
|
+
import { defineConfig, mergeConfig } from 'vite';
|
|
5
5
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
6
6
|
|
|
7
7
|
import checkDependencies from './plugins/check-dependencies.js';
|
|
@@ -66,7 +66,7 @@ export async function loadConfig({ cwd, filepath }) {
|
|
|
66
66
|
* @param {boolean} [options.build=false]
|
|
67
67
|
* @param {string} [configFilepath]
|
|
68
68
|
* @param {string} [cwd=process.cwd()]
|
|
69
|
-
* @returns {import('vite').UserConfig}
|
|
69
|
+
* @returns {Promise<import('vite').UserConfig>}
|
|
70
70
|
*/
|
|
71
71
|
export async function createConfig(
|
|
72
72
|
entries,
|
package/src/cli/getEntries.js
CHANGED
|
@@ -9,7 +9,8 @@ import { addExtension } from './utils.js';
|
|
|
9
9
|
* Build entries from entry filepath or folder path
|
|
10
10
|
* @param {string} entry
|
|
11
11
|
* @param {string} cwd - Current working directory
|
|
12
|
-
* @
|
|
12
|
+
* @param {string} command - Current working directory
|
|
13
|
+
* @returns {Promise<string[]>}
|
|
13
14
|
*/
|
|
14
15
|
export async function getEntries(
|
|
15
16
|
entry,
|
|
@@ -17,10 +18,18 @@ export async function getEntries(
|
|
|
17
18
|
command,
|
|
18
19
|
prefix = log.prefix(command),
|
|
19
20
|
) {
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param {string} message
|
|
24
|
+
*/
|
|
20
25
|
const displayCommand = (message) => {
|
|
21
26
|
p.note(bold(cyan(message)));
|
|
22
27
|
};
|
|
23
28
|
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param {string} message
|
|
32
|
+
*/
|
|
24
33
|
const onError = (message) => {
|
|
25
34
|
log.error(`Error\n`, prefix);
|
|
26
35
|
log.warn(message);
|
|
@@ -4,11 +4,20 @@ import { readFile } from 'node:fs/promises';
|
|
|
4
4
|
import { log, dim, green, yellow } from '../log.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @typedef
|
|
7
|
+
* @typedef Warning
|
|
8
|
+
* @property {string} type
|
|
9
|
+
* @property {string} importer
|
|
10
|
+
* @property {string} message
|
|
11
|
+
* @property {string} url
|
|
12
|
+
* @property {{ lineText: string }} location
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef ShaderUpdate
|
|
8
17
|
* @property {string} filepath - The path of the shader on the filesystem
|
|
9
18
|
* @property {string} source - The source code of the shader
|
|
10
19
|
* @property {boolean} nohsr - Whether the shader can be injected on the fly or if the sketch needs to be fully reloaded
|
|
11
|
-
* @property {
|
|
20
|
+
* @property {Warning[]} warnings - Indicates whether the Wisdom component is present.
|
|
12
21
|
*/
|
|
13
22
|
|
|
14
23
|
/**
|
|
@@ -28,8 +37,11 @@ export default function hotShaderReplacement({ cwd = process.cwd(), wss }) {
|
|
|
28
37
|
/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gi;
|
|
29
38
|
const base = process.cwd().split(path.sep).join(path.posix.sep);
|
|
30
39
|
|
|
40
|
+
/** @type Map<string, string[]> */
|
|
31
41
|
let dependencies = new Map();
|
|
42
|
+
/** @type {string[]} */
|
|
32
43
|
let shaders = [];
|
|
44
|
+
/** @type {import('vite').ModuleNode[]} */
|
|
33
45
|
let modulesToReload = [];
|
|
34
46
|
|
|
35
47
|
function reloadSketch() {
|
|
@@ -56,6 +68,12 @@ export default function hotShaderReplacement({ cwd = process.cwd(), wss }) {
|
|
|
56
68
|
return clone;
|
|
57
69
|
}
|
|
58
70
|
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {string} shaderSource
|
|
74
|
+
* @param {string} shaderPath
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
59
77
|
function addShaderFilepath(shaderSource, shaderPath) {
|
|
60
78
|
let keyword = `void main`;
|
|
61
79
|
let shaderParts = shaderSource.split(keyword);
|
|
@@ -67,10 +85,21 @@ ${keyword}${shaderParts[1]}
|
|
|
67
85
|
`;
|
|
68
86
|
}
|
|
69
87
|
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
* @param {string} shaderPath
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
70
93
|
function getUnixPath(shaderPath) {
|
|
71
94
|
return shaderPath.split(path.sep).join(path.posix.sep);
|
|
72
95
|
}
|
|
73
96
|
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @param {string} shaderSource
|
|
100
|
+
* @param {string} shaderPath
|
|
101
|
+
* @returns
|
|
102
|
+
*/
|
|
74
103
|
function compileGLSL(shaderSource, shaderPath) {
|
|
75
104
|
// test if shader source contains hint to avoid shader injection
|
|
76
105
|
const nohsr = ignoreRegex.test(shaderSource);
|
|
@@ -97,7 +126,8 @@ ${keyword}${shaderParts[1]}
|
|
|
97
126
|
* @param {string} parentSource
|
|
98
127
|
* @param {string} parentPath
|
|
99
128
|
* @param {string[]} deps
|
|
100
|
-
* @
|
|
129
|
+
* @param {Warning[]} warnings
|
|
130
|
+
* @returns {{ code: string, deps: string[], warnings: Warning[] }}
|
|
101
131
|
*/
|
|
102
132
|
function resolveDependencies(
|
|
103
133
|
parentSource,
|
|
@@ -163,7 +193,7 @@ ${keyword}${shaderParts[1]}
|
|
|
163
193
|
|
|
164
194
|
const parents = dependencies.get(chunkUnixPath);
|
|
165
195
|
|
|
166
|
-
if (!parents.includes(shaderPath)) {
|
|
196
|
+
if (parents && !parents.includes(shaderPath)) {
|
|
167
197
|
parents.push(shaderPath);
|
|
168
198
|
deps.push(chunkResolvedPath);
|
|
169
199
|
} else {
|
|
@@ -201,7 +231,11 @@ ${keyword}${shaderParts[1]}
|
|
|
201
231
|
|
|
202
232
|
return `${prefix}\n${chunkCode}`;
|
|
203
233
|
} catch (error) {
|
|
204
|
-
|
|
234
|
+
const err = /** @type {NodeJS.ErrnoException} */ (
|
|
235
|
+
error
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (err.code === 'ENOENT') {
|
|
205
239
|
warnings.push({
|
|
206
240
|
type: 'not found',
|
|
207
241
|
message: `Cannot find ${chunkResolvedPath}`,
|
|
@@ -237,8 +271,7 @@ ${keyword}${shaderParts[1]}
|
|
|
237
271
|
|
|
238
272
|
warnings.forEach((warning) => {
|
|
239
273
|
const { location } = warning;
|
|
240
|
-
|
|
241
|
-
const column = 4;
|
|
274
|
+
|
|
242
275
|
log.message(`${yellow(warning.type)} ${warning.importer}`, prefix);
|
|
243
276
|
console.log();
|
|
244
277
|
console.log(` ${dim(location.lineText)}`);
|
|
@@ -291,8 +324,8 @@ ${keyword}${shaderParts[1]}
|
|
|
291
324
|
name: 'fragment-plugin-hsr',
|
|
292
325
|
config: () => ({
|
|
293
326
|
optimizeDeps: {
|
|
294
|
-
|
|
295
|
-
|
|
327
|
+
rolldownOptions: {
|
|
328
|
+
moduleTypes: {
|
|
296
329
|
'.frag': 'text',
|
|
297
330
|
'.vert': 'text',
|
|
298
331
|
'.glsl': 'text',
|
|
@@ -305,7 +338,7 @@ ${keyword}${shaderParts[1]}
|
|
|
305
338
|
configureServer(_server) {
|
|
306
339
|
server = _server;
|
|
307
340
|
},
|
|
308
|
-
handleHotUpdate: async ({
|
|
341
|
+
handleHotUpdate: async ({ file, read }) => {
|
|
309
342
|
const { moduleGraph } = server;
|
|
310
343
|
|
|
311
344
|
if (fileRegex.test(file)) {
|
|
@@ -324,7 +357,7 @@ ${keyword}${shaderParts[1]}
|
|
|
324
357
|
nohsr,
|
|
325
358
|
} = compileGLSL(source, file);
|
|
326
359
|
|
|
327
|
-
/** @type ShaderUpdate
|
|
360
|
+
/** @type ShaderUpdate */
|
|
328
361
|
const shaderUpdate = {
|
|
329
362
|
filepath: unixPath,
|
|
330
363
|
source: glsl,
|
|
@@ -335,16 +368,21 @@ ${keyword}${shaderParts[1]}
|
|
|
335
368
|
return reloadShaders([shaderUpdate]);
|
|
336
369
|
} else {
|
|
337
370
|
if (dependencies.has(unixPath)) {
|
|
338
|
-
const shadersList = dependencies.get(unixPath);
|
|
371
|
+
const shadersList = dependencies.get(unixPath) ?? [];
|
|
339
372
|
|
|
340
373
|
// retrieve modules from module graph
|
|
341
|
-
const moduleNodes = shadersList
|
|
342
|
-
|
|
343
|
-
|
|
374
|
+
const moduleNodes = shadersList
|
|
375
|
+
.map((moduleNode) =>
|
|
376
|
+
moduleGraph.getModuleById(moduleNode),
|
|
377
|
+
)
|
|
378
|
+
.filter((moduleNode) => moduleNode !== undefined);
|
|
344
379
|
|
|
345
380
|
// save it as modules to reload to invalidate the top level shaders in case a dependency has been hot updated in between
|
|
346
|
-
|
|
381
|
+
if (moduleNodes.length > 0) {
|
|
382
|
+
modulesToReload.push(...moduleNodes);
|
|
383
|
+
}
|
|
347
384
|
|
|
385
|
+
/** @type {string[]} */
|
|
348
386
|
const sources = await Promise.all(
|
|
349
387
|
shadersList.map((shader) => {
|
|
350
388
|
return readFile(shader, 'utf-8');
|
package/src/cli/plugins/save.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import util from 'node:util';
|
|
3
|
+
import { exec as execSync } from 'node:child_process';
|
|
2
4
|
import { writeFile } from 'node:fs/promises';
|
|
3
5
|
import { json } from 'milliparsec';
|
|
4
|
-
import { log, green, red } from '../log.js';
|
|
6
|
+
import { log, green, red, yellow } from '../log.js';
|
|
5
7
|
import { mkdirp } from '../utils.js';
|
|
6
8
|
|
|
9
|
+
const exec = util.promisify(execSync);
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
*
|
|
9
13
|
* @param {object} [params]
|
|
@@ -45,6 +49,32 @@ export default function screenshot({
|
|
|
45
49
|
return directory;
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Check if a directory is within a git repository
|
|
54
|
+
* @param {string} dir - Directory to check
|
|
55
|
+
* @returns {Promise<boolean>}
|
|
56
|
+
*/
|
|
57
|
+
async function isGitRepository(directory) {
|
|
58
|
+
try {
|
|
59
|
+
await exec('git status --porcelain');
|
|
60
|
+
return true;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function commitChanges() {
|
|
67
|
+
const message = `fragment-auto-commit`;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
log.message(`${yellow(`git`)} Committing latest changes...`);
|
|
71
|
+
await exec(`git add . && git commit -m ${message}`);
|
|
72
|
+
log.message(`${green(`git`)} Committed latest changes.`);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
log.error(error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
let inlineExportDirPath;
|
|
49
79
|
|
|
50
80
|
return {
|
|
@@ -54,54 +84,83 @@ export default function screenshot({
|
|
|
54
84
|
server.middlewares.use(
|
|
55
85
|
json({
|
|
56
86
|
payloadLimit,
|
|
87
|
+
payloadLimitErrorFn: (payloadLimit) => {},
|
|
57
88
|
}),
|
|
58
89
|
);
|
|
59
90
|
server.middlewares.use('/save', async (req, res, next) => {
|
|
60
91
|
if (req.method === 'POST') {
|
|
61
|
-
const { files } = req.body;
|
|
62
|
-
|
|
63
92
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
if (req.body) {
|
|
94
|
+
const { files, commit: shouldCommit = false } =
|
|
95
|
+
req.body;
|
|
96
|
+
|
|
97
|
+
let canCommit = false;
|
|
98
|
+
|
|
99
|
+
if (shouldCommit) {
|
|
100
|
+
canCommit = await isGitRepository();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const filepaths = [];
|
|
104
|
+
const warnings = [];
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < files.length; i++) {
|
|
107
|
+
const { filename, data, encoding, exportDir } =
|
|
108
|
+
files[i];
|
|
109
|
+
|
|
110
|
+
let directory = resolveExportDirectory(
|
|
111
|
+
exportDir,
|
|
112
|
+
path.dirname(filename),
|
|
113
|
+
);
|
|
114
|
+
mkdirp(directory);
|
|
115
|
+
|
|
116
|
+
let filepath = path.join(
|
|
117
|
+
directory,
|
|
118
|
+
path.basename(filename),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
let buffer = Buffer.from(
|
|
122
|
+
encoding === 'base64'
|
|
123
|
+
? data.split(',')[1]
|
|
124
|
+
: data,
|
|
125
|
+
encoding,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await writeFile(filepath, buffer);
|
|
129
|
+
|
|
130
|
+
log.message(
|
|
131
|
+
`${green(`export`)} Saved ${filepath}`,
|
|
132
|
+
);
|
|
133
|
+
filepaths.push(filepath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (shouldCommit && canCommit) {
|
|
137
|
+
await commitChanges();
|
|
138
|
+
} else if (shouldCommit) {
|
|
139
|
+
const warning = `Auto-commit failed because the current folder is not a Git repository.`;
|
|
140
|
+
log.warn(warning);
|
|
141
|
+
warnings.push(warning);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
res.writeHead(200, {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
});
|
|
147
|
+
res.end(JSON.stringify({ filepaths, warnings }));
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error(`Payload is too big.`);
|
|
92
150
|
}
|
|
93
|
-
|
|
94
|
-
res.writeHead(200, {
|
|
95
|
-
'Content-Type': 'application/json',
|
|
96
|
-
});
|
|
97
|
-
res.end(JSON.stringify({ filepaths }));
|
|
98
151
|
} catch (error) {
|
|
99
|
-
|
|
152
|
+
const errorMessage = `Error while trying to save files on disk.`;
|
|
153
|
+
|
|
154
|
+
log.message(`${red(`export`)} ${errorMessage}`);
|
|
100
155
|
console.error(error);
|
|
101
156
|
res.writeHead(500, {
|
|
102
157
|
'Content-Type': 'application/json',
|
|
103
158
|
});
|
|
104
|
-
res.end(
|
|
159
|
+
res.end(
|
|
160
|
+
JSON.stringify({
|
|
161
|
+
errors: [errorMessage],
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
105
164
|
}
|
|
106
165
|
} else {
|
|
107
166
|
next();
|