initial crime
This commit is contained in:
commit
ea59080a5d
10 changed files with 505 additions and 0 deletions
68
package-lock.json
generated
Normal file
68
package-lock.json
generated
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"name": "bevy-cli",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "bevy-cli",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"mini-hamt": "^1.0.3",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"rambda": "^9.3.0",
|
||||||
|
"smol-toml": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@f/hash-str": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@f/hash-str/-/hash-str-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-qVQ416Lug2QuN3OW39+4CCDybWVABfAvyydlr9n1xwkE/U1XKB7BC2th1EH02f7P+zr5foycsNisppRKYf+L2Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@f/popcount": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@f/popcount/-/popcount-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-o6ctcKVEfiVLF2cPSdhR815dpVW4d3KijBu20jcxTbF6NFTV2sO/2+ffFn6NMgiVv3gv/4mVtOF92MYGtrfmew==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/mini-hamt": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-hamt/-/mini-hamt-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-kL1xAjkZl4tWZ2QYNBMbpvmt0QJRIOoMxmdnXtJyPn/6jrBr/TKRbyJRNTsrjcGxlaudR2+CmLPlko7B5IdZJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@f/hash-str": "^1.0.0",
|
||||||
|
"@f/popcount": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rambda": {
|
||||||
|
"version": "9.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rambda/-/rambda-9.3.0.tgz",
|
||||||
|
"integrity": "sha512-cl/7DCCKNxmsbc0dXZTJTY08rvDdzLhVfE6kPBson1fWzDapLzv0RKSzjpmAqP53fkQqAvq05gpUVHTrUNsuxg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/smol-toml": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/cyyynthia"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "bevy-cli",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/index.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"mini-hamt": "^1.0.3",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"rambda": "^9.3.0",
|
||||||
|
"smol-toml": "^1.3.0"
|
||||||
|
}
|
||||||
|
}
|
10
src/cargo.js
Normal file
10
src/cargo.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { promisify } from 'node:util'
|
||||||
|
import { exec as _exec } from 'node:child_process'
|
||||||
|
|
||||||
|
const exec = promisify(_exec)
|
||||||
|
|
||||||
|
export const Cargo = Object.freeze({
|
||||||
|
init: path => exec(`cargo new ${path}`),
|
||||||
|
add: lib => exec(`cargo add ${lib}`)
|
||||||
|
})
|
||||||
|
|
149
src/config.js
Normal file
149
src/config.js
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import npath from 'node:path'
|
||||||
|
import { assoc, concat, curry, curryN, lens, merge, pipe, prop, set, view } from 'rambda'
|
||||||
|
import { map, get as getMap } from './project.js'
|
||||||
|
import { get as getLinker } from './linker.js'
|
||||||
|
import { Cargo } from './cargo.js'
|
||||||
|
import { Crate } from './crates.js'
|
||||||
|
|
||||||
|
const optLevel = value => ({ 'opt-level': value })
|
||||||
|
|
||||||
|
//const assoc = curry((key, value, obj) => {
|
||||||
|
// const l = lens(prop(key), assoc(key))
|
||||||
|
// return set(l, value, obj)
|
||||||
|
//})
|
||||||
|
|
||||||
|
export const Bevy = Object.freeze({
|
||||||
|
active: () => true,
|
||||||
|
apply: curry(({ 'bevy-version': bevy }, project) =>
|
||||||
|
map(
|
||||||
|
'Cargo.toml',
|
||||||
|
assoc('dependencies', { bevy }),
|
||||||
|
project
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DynamicLinking = Object.freeze({
|
||||||
|
active: ({ dynlink }) => dynlink,
|
||||||
|
apply: curry(({ 'bevy-version': version }, project) =>
|
||||||
|
map(
|
||||||
|
'Cargo.toml',
|
||||||
|
assoc('dev-dependencies', { bevy: { version, features: ['dynamic_linking'] }}),
|
||||||
|
project
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DevelopmentOptimizations = Object.freeze({
|
||||||
|
active: ({ dynopts }) => dynopts,
|
||||||
|
apply: curry((_params, project) =>
|
||||||
|
map(
|
||||||
|
'Cargo.toml',
|
||||||
|
pipe(
|
||||||
|
assoc('profile.dev', optLevel(1)),
|
||||||
|
assoc('profile.dev.package."*"', optLevel(3))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ReleaseOptimizations = Object.freeze({
|
||||||
|
active: ({ relopts }) => relopts,
|
||||||
|
apply: curry((_params, project) => {
|
||||||
|
const relOpts = { 'codegen-units': 1, lto: 'thin' }
|
||||||
|
const wasmOpts = { inherits: 'release', 'opt-level': 's', strip: 'debuginfo' }
|
||||||
|
|
||||||
|
return map(
|
||||||
|
'Cargo.toml',
|
||||||
|
pipe(
|
||||||
|
assoc('profile.release', relOpts),
|
||||||
|
assoc('profile.wasm-release', wasmOpts)
|
||||||
|
),
|
||||||
|
project
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const CustomLinker = Object.freeze({
|
||||||
|
active: ({ linker }) => linker,
|
||||||
|
apply: curry(({ linker }, project) => {
|
||||||
|
const linkinfo = getLinker(linker, project.host)
|
||||||
|
|
||||||
|
if (!linkinfo) {
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
|
||||||
|
const { target, linker: bin, rustflags: rf1 = [] } = linkinfo
|
||||||
|
const config = '.cargo/config.toml'
|
||||||
|
const key = `target.${project.host}`
|
||||||
|
const { rustflags: rf2 = [], ...rest } = getMap(config, project)[key] || {}
|
||||||
|
|
||||||
|
return map(
|
||||||
|
config,
|
||||||
|
assoc(key, {
|
||||||
|
...rest,
|
||||||
|
linker: bin,
|
||||||
|
rustflags: concat(rf1, rf2)
|
||||||
|
}),
|
||||||
|
project
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const NightlyCompiler = Object.freeze({
|
||||||
|
active: ({ nightly }) => nightly,
|
||||||
|
apply: curry((_params, project) =>
|
||||||
|
map(
|
||||||
|
'rust-toolchain.toml',
|
||||||
|
assoc('toolchain', { channel: 'nightly' }),
|
||||||
|
project
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Cranelift = Object.freeze({
|
||||||
|
active: ({ cranelift, nightly }) => nightly && cranelift,
|
||||||
|
apply: curry((_params, project) =>
|
||||||
|
map(
|
||||||
|
'.cargo/config.toml',
|
||||||
|
pipe(
|
||||||
|
assoc('unstable', { 'codegen-backend': true }),
|
||||||
|
assoc('profile.dev', { 'codegen-backend': 'cranelift' }),
|
||||||
|
assoc('profile.dev.package."*"', { 'codegen-backend': 'llvm' }),
|
||||||
|
),
|
||||||
|
project
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const GenericSharing = Object.freeze({
|
||||||
|
active: ({ genshare, nightly }) => nightly && genshare,
|
||||||
|
apply: curry((_params, project) => {
|
||||||
|
const config = '.cargo/config.toml'
|
||||||
|
const key = `target.${project.host}`
|
||||||
|
const { rustflags = [], ...rest } = getMap(config, project)[key] || {}
|
||||||
|
|
||||||
|
return map(
|
||||||
|
config,
|
||||||
|
assoc(key, {
|
||||||
|
...rest,
|
||||||
|
rustflags: [...rustflags, '-Zshare-generics=y'],
|
||||||
|
}),
|
||||||
|
project
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const ParallelFrontend = Object.freeze({
|
||||||
|
active: ({ parallel, nightly }) => nightly && parallel,
|
||||||
|
apply: curry((_params, project) => {
|
||||||
|
const config = '.cargo/config.toml'
|
||||||
|
const key = `target.${project.host}`
|
||||||
|
const { rustflags = [], ...rest } = getMap(config, project)[key] || {}
|
||||||
|
|
||||||
|
return map(
|
||||||
|
config,
|
||||||
|
assoc(key, {
|
||||||
|
...rest,
|
||||||
|
rustflags: [...rustflags, '-Zthreads=0'],
|
||||||
|
}),
|
||||||
|
project
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
27
src/crates.js
Normal file
27
src/crates.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import npath from 'node:path'
|
||||||
|
import { request } from './http.js'
|
||||||
|
import { concat, identity, ifElse, isNotEmpty, join, map, pipe, toPairs } from 'rambda'
|
||||||
|
|
||||||
|
const UserAgent = { 'User-Agent': 'bevy-cli' }
|
||||||
|
|
||||||
|
export const RootURL = 'https://crates.io/api/v1'
|
||||||
|
|
||||||
|
const makeParams = ifElse(
|
||||||
|
isNotEmpty,
|
||||||
|
pipe(
|
||||||
|
toPairs,
|
||||||
|
map(join('=')),
|
||||||
|
join('&'),
|
||||||
|
concat('?')
|
||||||
|
),
|
||||||
|
() => identity('')
|
||||||
|
)
|
||||||
|
|
||||||
|
const get = (path, params = {}) => {
|
||||||
|
const url = [RootURL, path].join('/') + makeParams(params)
|
||||||
|
return request(url, { headers: UserAgent })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Crate = (crate, params) => get(npath.join('crates', crate), params)
|
||||||
|
export const CrateVersions = (crate, params) => get(npath.join('crates', crate, 'versions'), params)
|
||||||
|
|
54
src/http.js
Normal file
54
src/http.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { URL, urlToHttpOptions } from 'node:url'
|
||||||
|
import https from 'node:https'
|
||||||
|
|
||||||
|
export const Method = Object.freeze({
|
||||||
|
GET: 'GET',
|
||||||
|
HEAD: 'HEAD',
|
||||||
|
POST: 'POST',
|
||||||
|
PUT: 'PUT',
|
||||||
|
DELETE: 'DELETE',
|
||||||
|
CONNECT: 'CONNECT',
|
||||||
|
OPTIONS: 'OPTIONS',
|
||||||
|
TRACE: 'TRACE',
|
||||||
|
PATCH: 'PATCH'
|
||||||
|
})
|
||||||
|
|
||||||
|
const Response = data => Object.freeze({
|
||||||
|
buffer: () => Promise.resolve(data),
|
||||||
|
text: () => new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
resolve(data.toString())
|
||||||
|
} catch(e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
json: () => new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data))
|
||||||
|
} catch(e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const request = (path, options) => new Promise((resolve, reject) => {
|
||||||
|
const url = new URL(path)
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
...urlToHttpOptions(url),
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = https.request(opts, res => {
|
||||||
|
let data = []
|
||||||
|
|
||||||
|
res.on('data', d => {
|
||||||
|
data.push(d)
|
||||||
|
})
|
||||||
|
|
||||||
|
res.on('end', () => resolve(Response(Buffer.concat(data))))
|
||||||
|
})
|
||||||
|
req.on('error', reject)
|
||||||
|
req.end()
|
||||||
|
})
|
||||||
|
|
65
src/index.js
Normal file
65
src/index.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import pkg from '../package.json' with { type: 'json' }
|
||||||
|
import parseArgs from 'minimist'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { parse, stringify } from 'smol-toml'
|
||||||
|
import { reduce } from 'rambda'
|
||||||
|
import * as Project from './project.js'
|
||||||
|
import { Cargo } from './cargo.js'
|
||||||
|
import * as Configurations from './config.js'
|
||||||
|
import { Crate } from './crates.js'
|
||||||
|
|
||||||
|
const help = () => `Usage:
|
||||||
|
${pkg.name} PATH [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-b, --bevy-version the bevy version to install, default to latest stable
|
||||||
|
-l, --linker configured linker. valid options: lld, mold (linux only)
|
||||||
|
-q, --devopt development optimizations
|
||||||
|
-r, --relopt release optimizations
|
||||||
|
-d, --dynlink enable dynamic linking
|
||||||
|
-n, --nightly enable nightly rust compiler
|
||||||
|
-c, --cranelift enable cranelift codegen (requires nightly)
|
||||||
|
-g, --genshare enable generic sharing (requires nightly)
|
||||||
|
-p, --parallel enable rustc's parallel frontend (requires nightly)
|
||||||
|
|
||||||
|
-h, --help print usage information and exit
|
||||||
|
-v, --version show version info and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
const applicableConfigs = params =>
|
||||||
|
Object.values(Configurations).filter(c => c.active(params))
|
||||||
|
|
||||||
|
const version = () => `${pkg.name} v${pkg.version}`
|
||||||
|
|
||||||
|
const main = async argv => {
|
||||||
|
if(argv.help) {
|
||||||
|
console.log(help())
|
||||||
|
} else if (argv.version) {
|
||||||
|
console.log(version())
|
||||||
|
} else {
|
||||||
|
const [relativeRoot] = argv._
|
||||||
|
const root = path.join(process.cwd(), relativeRoot)
|
||||||
|
await Cargo.init(root)
|
||||||
|
const project = Project.create(path.normalize(root))
|
||||||
|
const configs = applicableConfigs(argv)
|
||||||
|
const result = await reduce(async (acc, cfg) => cfg.apply(argv, await acc), project, configs)
|
||||||
|
await Project.serialize(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultBevyVersion = () =>
|
||||||
|
Crate('bevy').then(c => c.json()).then(c => c.crate.default_version)
|
||||||
|
|
||||||
|
const defaultLinker = () => process.platform === 'darwin' ? undefined : 'lld'
|
||||||
|
|
||||||
|
const schema = Object.freeze({
|
||||||
|
string: ['bevy-version', 'linker'],
|
||||||
|
boolean: ['devopt', 'relopt', 'dynlink', 'nightly', 'cranelift', 'genshare', 'parallel', 'version'],
|
||||||
|
alias: { b: 'bevy-version', l: 'linker', g: 'genshare', c: 'cranelift', n: 'nightly', d: 'dynlink', p: 'parallel', v: 'version', h: 'help' },
|
||||||
|
default: { 'bevy-version': await defaultBevyVersion(), devopt: true, relopt: true, genshare: true, parallel: true, cranelift: true, nightly: true, dynlink: true, linker: defaultLinker() }
|
||||||
|
})
|
||||||
|
|
||||||
|
const argv = parseArgs(process.argv.slice(2), schema)
|
||||||
|
|
||||||
|
main(argv)
|
||||||
|
|
44
src/linker.js
Normal file
44
src/linker.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { path } from 'rambda'
|
||||||
|
|
||||||
|
const Linker = ({ target, linker, rustflags }) => Object.freeze({ target, linker, rustflags })
|
||||||
|
|
||||||
|
const linuxLld = () => Linker({
|
||||||
|
linker: 'clang',
|
||||||
|
rustflags: ['-C', 'link-arg=-fuse-ld=lld']
|
||||||
|
})
|
||||||
|
|
||||||
|
const macosLld = () => Linker({
|
||||||
|
rustflags: [ '-C', 'link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld']
|
||||||
|
})
|
||||||
|
|
||||||
|
const windowsLld = () => Linker({
|
||||||
|
linker: 'rust-lld.exe',
|
||||||
|
rustflags: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const linuxMold = () => Linker({
|
||||||
|
linker: 'clang',
|
||||||
|
rustflags: ['-C', 'link-arg=-fuse-ld=/usr/bin/mold']
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export const LinkerType = Object.freeze({
|
||||||
|
lld: Object.freeze({
|
||||||
|
'x86_64-pc-windows-msvc': windowsLld,
|
||||||
|
'aarch64-apple-darwin': macosLld,
|
||||||
|
'x86_64-unknown-linux-gnu': linuxLld
|
||||||
|
}),
|
||||||
|
mold: Object.freeze({
|
||||||
|
'x86_64-unknown-linux-gnu': linuxMold
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const get = (linker, host)=> {
|
||||||
|
const type = path([linker.toLowerCase(), host], LinkerType)
|
||||||
|
|
||||||
|
if(type) {
|
||||||
|
return type()
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
68
src/project.js
Normal file
68
src/project.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import npath from 'node:path'
|
||||||
|
import { parse, stringify } from 'smol-toml'
|
||||||
|
import { always, cond, curry, equals, flip, partial, pipe, T as True } from 'rambda'
|
||||||
|
import * as hamt from 'mini-hamt'
|
||||||
|
|
||||||
|
const guessHostTriplet = cond([
|
||||||
|
[equals('win32'), always('x86_64-pc-windows-msvc')],
|
||||||
|
[equals('darwin'), always('aarch64-apple-darwin')],
|
||||||
|
[True, always('x86_64-unknown-linux-gnu')],
|
||||||
|
])
|
||||||
|
|
||||||
|
export const create = (root, host = guessHostTriplet(process.platform), modified = new Set(), config = hamt.empty) => Object.freeze({
|
||||||
|
root,
|
||||||
|
host,
|
||||||
|
modified,
|
||||||
|
config
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolve = curry((path, { root }) => {
|
||||||
|
return npath.isAbsolute(path) ? path : npath.join(root, path)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const load = curry((path, project) => {
|
||||||
|
const absPath = resolve(path, project)
|
||||||
|
|
||||||
|
return fs.readFile(absPath, { encoding: 'utf-8' })
|
||||||
|
.then(parse)
|
||||||
|
.catch(() => ({}))
|
||||||
|
.then(value => set(path, value, project))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const get = curry((path, { config }) =>
|
||||||
|
hamt.get(config, path)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const set = curry((path, value, { root, host, modified, config }) =>
|
||||||
|
create(
|
||||||
|
root,
|
||||||
|
host,
|
||||||
|
new Set([path, ...modified]),
|
||||||
|
hamt.set(config, path, value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const map = curry(async (path, fn, project) => {
|
||||||
|
let p = project
|
||||||
|
let f = get(path, project)
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
p = await load(path, project)
|
||||||
|
f = get(path, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = fn(f)
|
||||||
|
return set(path, r, p)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const serialize = async project => {
|
||||||
|
for(const path of Array.from(project.modified)) {
|
||||||
|
const absPath = resolve(path, project)
|
||||||
|
const dir = npath.dirname(absPath)
|
||||||
|
await fs.mkdir(dir, { recursive: true })
|
||||||
|
const data = Buffer.from(stringify(get(path, project)))
|
||||||
|
fs.writeFile(absPath, data, { flag: 'w' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
0
src/util.js
Normal file
0
src/util.js
Normal file
Loading…
Reference in a new issue