diff --git a/config/dependencies.yaml b/config/dependencies.yaml index 5e90a06786a..6188b475083 100644 --- a/config/dependencies.yaml +++ b/config/dependencies.yaml @@ -102,6 +102,8 @@ new-design: 'ora': '^6.1.0' 'prompts': '^2.4.2' 'recursive-readdir': '^2.2.2' + peer: + '@freesewing/core': *freesewing noble: peer: '@freesewing/bella': *freesewing diff --git a/packages/new-design/lib/utils.mjs b/packages/new-design/lib/utils.mjs index b19a12c9885..0efe93d2bae 100644 --- a/packages/new-design/lib/utils.mjs +++ b/packages/new-design/lib/utils.mjs @@ -1,6 +1,6 @@ import { config } from './config.mjs' -import { mkdir, readFile, writeFile, copyFile } from 'node:fs/promises' -import { join, dirname } from 'path' +import { mkdir, readFile, writeFile, copyFile, stat } from 'node:fs/promises' +import { join, dirname, relative } from 'path' import mustache from 'mustache' import rdir from 'recursive-readdir' import chalk from 'chalk' @@ -9,18 +9,22 @@ import { oraPromise } from 'ora' import { execa } from 'execa' import axios from 'axios' import { fileURLToPath } from 'url' +import { capitalize } from '@freesewing/core' // Current working directory -let cwd +let filename try { - cwd = __dirname + filename = __filename } catch { - cwd = dirname(fileURLToPath(import.meta.url)) + filename = fileURLToPath(import.meta.url) } +const newDesignDir = join(filename, '../..') +const monorepoDesignsDir = join(newDesignDir, '../../designs') const nl = '\n' const tab = ' ' const nlt = nl + tab +const designSrcDir = 'design/src' // Checks for node 16 or higher export const checkNodeVersion = () => { @@ -114,70 +118,64 @@ export const getChoices = async () => { } // Keep track of directories that need to be created -const dirs = {} +const dirPromises = {} const ensureDir = async (file, suppress = false) => { const dir = suppress ? dirname(file.replace(suppress)) : dirname(file) - if (!dirs[dir]) { - await mkdir(dir, { recursive: true }) - dirs[dir] = true + if (!dirPromises[dir]) { + dirPromises[dir] = mkdir(dir, { recursive: true }) } -} - -/** ensure a template file has a directory, then use mustache to render and save it */ -const writeTemplateFile = async (to, template, args) => { - if (!dirs[to]) await ensureDir(to) - - return writeFile(to, mustache.render(template, args)) + await dirPromises[dir] } // Helper method to copy template files -const copyTemplate = async (config, choices) => { - // Copy files in parallel rather than using await - const promises = [] +const copyFileOrTemplate = async (fromRootOrTemplate, toRoot, relativeDest, templateVars) => { + const to = join(toRoot, relativeDest) - // const templateConfig - // Copy shared files - for (const from of config.files.shared) { - // FIXME: Explain the -7 - const to = join(config.dest, from.slice(config.source.shared.length - 7)) - if (!dirs[to]) await ensureDir(to) - promises.push(copyFile(from, to)) + await ensureDir(to) + + if (templateVars) { + const rendered = mustache.render(fromRootOrTemplate, templateVars) + await writeFile(to, rendered) + } else { + const from = join(fromRootOrTemplate, relativeDest) + await copyFile(from, to) } +} - // Template the package.json - const packageJsonTemplate = await readFile(config.files.templates['package.json'], 'utf-8') - const packageJsonTo = join(config.dest, 'package.json') - promises.push( - writeTemplateFile(packageJsonTo, packageJsonTemplate, { - name: choices.name, - tag: config.tag, - dependencies: config.templateData.dependencies, - }) +// Template the package.json +const copyPackageJson = async (config, choices) => { + const packageJsonTemplate = await readFile( + config.relativeFiles.templates['package.json'], + 'utf-8' ) + await copyFileOrTemplate(packageJsonTemplate, config.dest, 'package.json', { + name: choices.name, + tag: config.tag, + dependencies: config.templateData.dependencies, + }) +} - // design files go here - const designSrcDir = join(config.dest, 'design/src') - +// Template the design index file +const copyIndexFile = async (config, choices) => { // Template the index file - const indexTemplate = await readFile(config.files.templates['index'], 'utf-8') - const indexTo = join(designSrcDir, 'index.mjs') + const indexTemplate = await readFile(config.relativeFiles.templates['index'], 'utf-8') - // does this base have parts with a lot of attending config? - const complexParts = typeof config.templateData.parts[0] === 'object' // get the part names based on how they are given in the configuration - const partNames = complexParts + const partNames = config.complexParts ? config.templateData.parts.map((p) => p.part) : config.templateData.parts // write the file - promises.push( - writeTemplateFile(indexTo, indexTemplate, { - name: choices.name, - parts: partNames, - }) - ) + await copyFileOrTemplate(indexTemplate, config.dest, `${designSrcDir}/index.mjs`, { + name: choices.name, + Name: capitalize(choices.name), + parts: partNames, + }) +} +// Template the part files +const copyPartFiles = async (config, choices) => { // Template the parts - const partTemplate = await readFile(config.files.templates.part, 'utf-8') + const partTemplate = await readFile(config.relativeFiles.templates.part, 'utf-8') // does this design inherit from another? const doesInherit = !config.templateData.noInheritance @@ -191,33 +189,52 @@ const copyTemplate = async (config, choices) => { // if it inherits, we also need the name of the design it inherits from if (doesInherit) { baseConfig.baseName = choices.template - baseConfig.baseNameUpcase = choices.template[0].toUpperCase() + choices.template.slice(1) + baseConfig.BaseName = capitalize(choices.template) } // for each part - config.templateData.parts.forEach((p) => { + return config.templateData.parts.map((p) => { // set up the arguments based on what's in the part's config - const templateArgs = complexParts + const templateArgs = config.complexParts ? { - ...baseConfig, - part: p, - } - : { ...baseConfig, ...p, } + : { + ...baseConfig, + part: p, + } // add an uppercase version of the partName - templateArgs.partUpcase = templateArgs.part[0].toUpperCase() + templateArgs.part.slice(1) + templateArgs.Part = capitalize(templateArgs.part) // write the part file - const to = join(designSrcDir, `${templateArgs.part}.mjs`) - promises.push(writeTemplateFile(to, partTemplate, templateArgs)) + return copyFileOrTemplate( + partTemplate, + config.dest, + `${designSrcDir}/${templateArgs.part}.mjs`, + templateArgs + ) }) +} + +// Helper method to copy template files +const copyAll = async (config, choices) => { + let promises = [] + + // Copy shared files + promises = promises.concat( + config.relativeFiles.shared.map((from) => { + copyFileOrTemplate(config.source.shared, config.dest, from) + }) + ) + + // template design files + promises.push(copyPackageJson(config, choices)) + promises.push(copyIndexFile(config, choices)) + promises = promises.concat(copyPartFiles(config, choices)) await Promise.all(promises) - - return } // Helper method to run [yarn|npm] install @@ -231,25 +248,25 @@ const installDependencies = async (config, choices) => const downloadLabFiles = async (config) => { const promises = [] for (const dir in config.fetch) { - for (const file of config.fetch[dir]) { - const to = typeof file === 'string' ? join(config.dest, file) : join(config.dest, file.to) - if (!dirs[to]) await ensureDir(to) - promises.push( - axios - .get( + promises.push( + ...config.fetch[dir].map(async (file) => { + const to = typeof file === 'string' ? join(config.dest, file) : join(config.dest, file.to) + await ensureDir(to) + try { + const res = await axios.get( `${config.fileUri}/${config.repo}/${config.branch}/${dir}/${ typeof file === 'string' ? file : file.from }` ) - .catch((err) => console.log(err)) - .then((res) => promises.push(writeFile(to, res.data))) - ) - } + await writeFile(to, res.data) + } catch (err) { + console.log(err) + } + }) + ) } - await Promise.all(promises) - - return + return Promise.all(promises) } // Helper method to initialize a git repository @@ -322,40 +339,49 @@ const showTips = (config, choices) => { // Creates the environment based on the user's choices export const createEnvironment = async (choices) => { // Store directories for re-use - config.cwd = cwd config.source = { - root: cwd, - templateData: cwd + `/../templates/from-${choices.template}.mjs`, - templates: join(cwd, `/../templates/shared`), - shared: cwd + `/../shared`, + templateData: join(newDesignDir, `templates/from-${choices.template}.mjs`), + templates: join(newDesignDir, `templates/shared`), + shared: join(newDesignDir, `shared`), } + config.dest = join(process.cwd(), choices.name) // Create target directory await mkdir(config.dest, { recursive: true }) - const templateFiles = await rdir(config.source.templates) + // get the template files in a dictionary const templates = {} - templateFiles.forEach( - (f) => - (templates[f.replace(`${config.source.templates}/`, '').replace(/(\.mjs)*\.mustache/, '')] = - f) - ) + const templateFiles = await rdir(config.source.templates) + templateFiles.forEach((file) => { + const relativeName = relative(config.source.templates, file).replace(/(\.mjs)*\.mustache/, '') + templates[relativeName] = file + }) - // Find files - config.files = { + config.relativeFiles = { templates, - shared: await rdir(config.source.shared), + shared: (await rdir(config.source.shared)).map((file) => relative(config.source.shared, file)), } config.templateData = await import(config.source.templateData) + // does this base have parts with a lot of attending config? + config.complexParts = typeof config.templateData.parts[0] === 'object' // Output a linebreak console.log() // Copy/Template files try { - await oraPromise(copyTemplate(config, choices), { + try { + await stat(join(monorepoDesignsDir, choices.template)) + if (choices.template !== 'tutorial') { + templateVars.block = choices.template + } + } catch (err) { + // fs.stat throws an error if no such file or directory exists + } + + await oraPromise(copyAll(config, choices), { text: chalk.white.bold('🟨⬜⬜⬜ Copying template files') + chalk.white.dim(' | Just a moment'), diff --git a/packages/new-design/package.json b/packages/new-design/package.json index 0a241502217..292a2b2245f 100644 --- a/packages/new-design/package.json +++ b/packages/new-design/package.json @@ -28,6 +28,7 @@ }, "peerDependencies": {}, "dependencies": { + "@freesewing/core": "^3.0.0-alpha.2", "axios": "^1.1.2", "chalk": "^5.0.1", "execa": "^6.1.0", diff --git a/packages/new-design/templates/shared/index.mjs.mustache b/packages/new-design/templates/shared/index.mjs.mustache index cd431bd9efe..2aeb2f65833 100644 --- a/packages/new-design/templates/shared/index.mjs.mustache +++ b/packages/new-design/templates/shared/index.mjs.mustache @@ -1,12 +1,18 @@ +//{{! +// Change the Mustache delimiter from double curly braces to double dollar signs. +// Dollar signs are allowed in EcmaScript identifier names, +// which is helpful when running unrendered Mustache templates through eslint. +//}}{{=$$ $$=}} + // Import Design constructor import { Design } from '@freesewing/core' // Import parts -{{#parts}} -import { {{.}} } from './{{.}}.mjs' -{{/parts}} +$$#parts$$ +import { $$.$$ } from './$$.$$.mjs' +$$/parts$$ // Create the new design -const Pattern = new Design({ +const $$Name$$ = new Design({ data: { /* * If you like, you can add any data you want to your design. @@ -15,17 +21,19 @@ const Pattern = new Design({ * If you don't use this, * you can remove this data key enterely. */ - name: "{{ name }}", + name: "$$ Name $$", }, // A list of parts is all that is required. - parts: [ {{parts}} ], + parts: [ $$parts$$ ], }) +const Pattern = $$Name$$ /* * Named exports * * We export the design itself as well as each part individually. * This allows us to re-use these parts in other designs. */ -export { {{#parts}}{{.}}, {{/parts}}Pattern } + +export { $$#parts$$ $$.$$ , $$/parts$$ $$Name$$, Pattern } diff --git a/packages/new-design/templates/shared/part.mjs.mustache b/packages/new-design/templates/shared/part.mjs.mustache index d88faebf987..6f9d3f5add6 100644 --- a/packages/new-design/templates/shared/part.mjs.mustache +++ b/packages/new-design/templates/shared/part.mjs.mustache @@ -1,11 +1,11 @@ {{#doesInherit}} -import { {{part}} as {{baseName}}{{partUpcase}} } from '@freesewing/{{baseName}}' +import { {{part}} as {{baseName}}{{Part}} } from '@freesewing/{{baseName}}' {{/doesInherit}} {{#pluginBundle}} import { pluginBundle } from '@freesewing/plugin-bundle' {{/pluginBundle}} -function draft{{partUpcase}} ({ +function draft{{Part}} ({ // Uncomment below to destructure what you need /* * Content constructors @@ -71,7 +71,7 @@ export const {{part}} = { * * Documentation: https://freesewing.dev/reference/api/part/draft */ - draft: draft{{partUpcase}}, + draft: draft{{Part}}, after: [ /* * after: Holds a list of parts that should be drafted prior to this part. @@ -87,14 +87,16 @@ export const {{part}} = { /* * from: Holds the part you want to extend. * - * Since you opted to extend {{baseNameUpcase}}, and this is the {{part}} part, - * we're extending {{baseNameUpcase}}'s {{part}} part here. - * It was imported at the top of this file from @freesewing/{{baseNameUpcase}} + {{#doesInherit}} + * Since you opted to extend {{BaseName}}, and this is the {{part}} part, + * we're extending {{BaseName}}'s {{part}} part here. + * It was imported at the top of this file from @freesewing/{{BaseName}} * + {{/doesInherit}} * Documentation: https://freesewing.dev/reference/api/part/config/dependencies */ {{#doesInherit}} - from: {{baseName}}{{partUpcase}}, + from: {{baseName}}{{Part}}, {{/doesInherit}} {{^doesInherit}} from: false, @@ -103,15 +105,17 @@ export const {{part}} = { * hide: Set this to true to hide a part. * * We've set this to false here to clarify its use. - * I you don't want to hide this part, + * If you don't want to hide this part, * you can remove the hide key entirely. */ hide: false, /* * hideDependecies: Set this to true to hide a part's dependencies. * - * We've set this to true here since you're extending {{baseNameUpcase}}'s {{part}} part. - * I you don't want to hide this part's dependencies, + {{#doesInherit}} + * We've set this to true here since you're extending {{BaseName}}'s {{part}} part. + {{/doesInherit}} + * If you don't want to hide this part's dependencies, * you can remove the hideDependencies key entirely. */ hideDependencies: {{doesInherit}}, @@ -120,8 +124,8 @@ export const {{part}} = { * * This is a combination of the hide and hideDependencies keys in case * you want to both hide this part and its dependencies. - * We've included it here with a value of false to its use. - * I you don't want to hide this a part and its dependencies, + * We've included it here with a value of false. + * If you don't want to hide this a part and its dependencies, * you can remove the hideAll key entirely. */ hideAll: false, @@ -132,7 +136,7 @@ export const {{part}} = { * Declare options used in this part here. {{#doesInherit}} * You only need to add additional options. - * All options coming from {{baseNameUpcase}}'s {{part}} part are already loaded. + * All options coming from {{BaseName}}'s {{part}} part are already loaded. {{/doesInherit}} * * If you don't have any options to add, @@ -149,7 +153,7 @@ export const {{part}} = { * Declare measurements required by this part here. {{#doesInherit}} * You only need to add additional measurements. - * All measurements coming from {{baseNameUpcase}}'s {{part}} part are already loaded. + * All measurements coming from {{BaseName}}'s {{part}} part are already loaded. {{/doesInherit}} * * If you don't have any required measurements to add, @@ -177,7 +181,7 @@ export const {{part}} = { * Add all the plugins here that you need in this part. {{#doesInherit}} * You only need to add additional plugins. - * All plugins coming from {{baseNameUpcase}}'s {{part}} part are already loaded. + * All plugins coming from {{BaseName}}'s {{part}} part are already loaded. {{/doesInherit}} * * If you don't have any plugins to add, diff --git a/packages/new-design/templates/shared/scratch-part.mjs.mustache b/packages/new-design/templates/shared/scratch-part.mjs.mustache deleted file mode 100644 index ce548652cc9..00000000000 --- a/packages/new-design/templates/shared/scratch-part.mjs.mustache +++ /dev/null @@ -1,217 +0,0 @@ -{{#pluginBundle}} -import { pluginBundle } from '@freesewing/plugin-bundle' -{{/pluginBundle}} - -function draft{{partUpcase}}({ - // Uncomment below to destructure what you need - /* - * Content constructors - */ - Path, // A Path constructor to create new paths - Point, // A Point constructor to create new points - Snippet, // A Snippet constructor to create new snippets - /* - * Content constainers - */ - paths, // Add a Path to your part by adding it to this object - points, // Add a Points to your part by adding it to this object - snippets, // Add a Snippet to your part by adding it to this object - /* - * Access to settings - */ - //absoluteOptions, // Access to settings.absoluteOptions - complete, // Access to settings.complete - //measurements, // Access to settings.measurements - options, // Access to settings.options - paperless, // Access to settings.paperless - sa, // Access to settings.sa - //scale, // Access to settings.scale - /* - * Access to utilities - */ - //getId, //See the getId documentation - //hide, //See the hide documentation - //log, //See the logging documentation - macro, //See the macros documentation - //setHidden, //See the setHidden documentation - //store, //See the store documentation - //unhide, //See the unhide documentation - //units, //See the units documentation - //utils, //See the utils documentation - /* - * Return value - */ - part, // Your draft method must return this -}) { - - // Add points to make a box - const w = 500 * options.size - points.topLeft = new Point(0, 0) - points.topRight = new Point(w, 0) - points.bottomLeft = new Point(0, w / 2) - points.bottomRight = new Point(w, w / 2) - - // Create a path for the box outline - paths.seam = new Path() - .move(points.topLeft) - .line(points.bottomLeft) - .line(points.bottomRight) - .line(points.topRight) - .line(points.topLeft) - .close() - .setClass('fabric') - - // Complete? - if (complete) { - // Add a logo - points.logo = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5) - snippets.logo = new Snippet('logo', points.logo) - - // Add some text - points.text = points.logo - .shift(-90, w / 8) - .addText('FreeSewing', 'center') - - if (sa) { - // Add seam allowance - paths.sa = paths.seam.offset(sa).setClass('fabric sa') - } - } - - // Add dimensions for paperless mode - if (paperless) { - macro('hd', { - from: points.bottomLeft, - to: points.bottomRight, - y: points.bottomLeft.y + sa + 15, - }) - macro('vd', { - from: points.bottomRight, - to: points.topRight, - x: points.topRight.x + sa + 15, - }) - } - - return part -} - -export const {{part}} = { - /* - * name: Holds the name of this part. - * - * We STRONGLY recommend naming your parts in the format of - * design.part to avoid naming conflicts when people re-use - * parts across designs. - */ - name: '{{ name }}.{{part}}', - /* - * draft: Holds the draft method for this part - * - * This should be a function that drafts and returns the part - * - * Documentation: https://freesewing.dev/reference/api/part/draft - */ - draft: draft{{partUpcase}}, - after: [ - /* - * after: Holds a list of parts that should be drafted prior to this part. - * - * You'll need to import these parts, just as with the from key above. - * - * If you don't have any parts to draft prior to this part, - * you can remove this options key entirely. - * - * Documentation: https://freesewing.dev/reference/api/part/config/dependencies - */ - ], - /* - * from: Holds the part you want to extend. - * - * Since we're not extending anything here we set it to false. - * - * In a situation like this where you are not extending a part - * you can remove the from key entirely. - * - * Documentation: https://freesewing.dev/reference/api/part/config/dependencies - */ - from: false, - /* - * hide: Set this to true to hide a part. - * - * We've set this to false here to clarify its use. - * I you don't want to hide this part, - * you can remove the hide key entirely. - */ - hide: false, - /* - * hideDependecies: Set this to true to hide a part's dependencies. - * - * I you don't want to hide this part's dependencies, - * you can remove the hideDependencies key entirely. - */ - hideDependencies: false, - /* - * hideAll: Set this to true to hide both the part and its dependencies. - * - * This is a combination of the hide and hideDependencies keys in case - * you want to both hide this part and its dependencies. - * We've included it here with a value of false to its use. - * I you don't want to hide this a part and its dependencies, - * you can remove the hideAll key entirely. - */ - hideAll: false, - options: { - /* - * options: Holds (the configuration of) options for this part - * - * Declare options used in this part here. - * - * If you don't have any options to add, - * you can remove this options key entirely. - * - * Documentation: https://freesewing.dev/reference/api/part/config/options - */ - {{#options}} - {{.}}, - {{/options}} - }, - measurements: [ - /* - * measurements: Holds a list of measurements required by this part. - * - * Declare measurements required by this part here. - * - * If you don't have any required measurements to add, - * you can remove this measurements key entirely. - * - * Documentation: https://freesewing.dev/reference/api/part/config/measurements - */ - ], - optionalMeasurements: [ - /* - * optionalMeasurements: Holds a list of measurements optional in this part. - * - * Declare measurements that are optional for this part here. - * - * If you don't have any optional measurements to add, - * you can remove this optionalMeasurements key entirely. - * - * Documentation: https://freesewing.dev/reference/api/part/config/measurements - */ - ], - plugins: [ - /* - * plugins: Holds a list of plugins this part relies on. - * - * Add all the plugins here that you need in this part. - * - * If you don't have any plugins to add, - * you can remove this plugins key entirely. - * - * Documentation: https://freesewing.dev/reference/api/part/config/plugins - */ - {{#pluginBundle}} - pluginBundle, - {{/pluginBundle}} - ] -}