diff --git a/package-lock.json b/package-lock.json index c788fe4..16c2545 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8093,6 +8093,32 @@ "@ts-jison/lexer": "^0.4.1-alpha.1" } }, + "node_modules/@ts-morph/common": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", + "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", + "license": "MIT", + "dependencies": { + "minimatch": "^9.0.4", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.9" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "devOptional": true, @@ -8453,7 +8479,9 @@ } }, "node_modules/@types/jsonld": { - "version": "1.5.13", + "version": "1.5.15", + "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.15.tgz", + "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==", "license": "MIT" }, "node_modules/@types/keygrip": { @@ -8569,6 +8597,17 @@ "version": "2.7.3", "license": "MIT" }, + "node_modules/@types/prompts": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", + "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "kleur": "^3.0.3" + } + }, "node_modules/@types/prop-types": { "version": "15.7.11", "dev": true, @@ -8713,6 +8752,8 @@ }, "node_modules/@types/shexj": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/shexj/-/shexj-2.1.4.tgz", + "integrity": "sha512-/dcF8vT/CHseZxNTcWR+otf6018PACgHiKFukPYsxQCRppGZq0UcALMedZUUnj7IM4WOesFoERwJBhEw44d/VQ==", "dev": true, "license": "MIT" }, @@ -11031,6 +11072,12 @@ "node": ">=4" } }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT" + }, "node_modules/codepage": { "version": "1.15.0", "dev": true, @@ -21261,6 +21308,12 @@ "tslib": "^2.0.3" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "license": "MIT", @@ -22759,6 +22812,8 @@ }, "node_modules/prompts": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -26605,6 +26660,45 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.2.1", "dev": true, @@ -26713,6 +26807,16 @@ "version": "0.1.13", "license": "Apache-2.0" }, + "node_modules/ts-morph": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz", + "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.25.0", + "code-block-writer": "^13.0.3" + } + }, "node_modules/ts-node": { "version": "10.9.2", "devOptional": true, @@ -28640,6 +28744,7 @@ "version": "0.0.1-alpha.28", "license": "MIT", "dependencies": { + "@ldo/ldo": "^0.0.1-alpha.28", "@ldo/schema-converter-shex": "^0.0.1-alpha.24", "@shexjs/parser": "^1.0.0-alpha.24", "child-process-promise": "^2.2.1", @@ -28647,17 +28752,23 @@ "ejs": "^3.1.8", "fs-extra": "^10.1.0", "loading-cli": "^1.1.0", - "prettier": "^3.0.3" + "prettier": "^3.0.3", + "prompts": "^2.4.2", + "ts-morph": "^24.0.0", + "type-fest": "^2.19.0" }, "bin": { "ldo": "dist/index.js" }, "devDependencies": { + "@ldo/cli": "^0.0.1-alpha.28", "@types/child-process-promise": "^2.2.2", "@types/ejs": "^3.1.1", "@types/fs-extra": "^9.0.13", "@types/jest": "^27.0.3", - "@types/shexj": "2.1.4", + "@types/jsonld": "^1.5.15", + "@types/prompts": "^2.4.9", + "@types/shexj": "^2.1.4", "copyfiles": "^2.4.1", "jest": "^27.4.2", "rimraf": "^3.0.2", @@ -28726,6 +28837,18 @@ } } }, + "packages/cli/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/cli/node_modules/typescript": { "version": "4.9.5", "dev": true, diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 0000000..138f9c6 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1,2 @@ +example-create +example-init \ No newline at end of file diff --git a/packages/cli/README.md b/packages/cli/README.md index 2200e69..e88f798 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -53,9 +53,20 @@ This will generate five files: - `./ldo/foafProfile.schema.ts` - `./ldo/foafProfile.context.ts` +## Creating a new project to distribure shapes + +Sometimes, you might want to distribute shapes to others. The easiest way to do that is to deploy them to NPM. The LDO CLI has an easy-to-use command for generating a standalone project just for your shapes. + +```bash +npx @ldo/cli create ./my-project +``` + +This script will generate a project with a place to put your shapes. Running `npm publish` will build the shapes and push to project to NPM for you. + ## API Details - [`init` command](https://ldo.js.org/api/cli/init/) - [`build` command](https://ldo.js.org/api/cli/build/) + - [`create` command](https://ldo.js.org/api/cli/create/) ``` ## Sponsorship diff --git a/packages/cli/example-init-placeholder/package.json b/packages/cli/example-init-placeholder/package.json new file mode 100644 index 0000000..f41a7f6 --- /dev/null +++ b/packages/cli/example-init-placeholder/package.json @@ -0,0 +1,11 @@ +{ + "name": "example-init", + "version": "1.0.0", + "description": "", + "keywords": [ + "" + ], + "author": "", + "license": "MIT", + "main": "./index.js" +} \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 80759b4..a8b5899 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -7,20 +7,16 @@ "ldo": "./dist/index.js" }, "scripts": { - "start": "node dist/index.js build", - "start:init": "node dist/index.js init", - "dev": "npm run build && npm run start:init", - "build": "npm run clean && npm run build:ts && npm run copy-files", + "build": "npm run clean && npm run build:ts && npm run copy-files && npm run update-permission", "build:ts": "tsc --project tsconfig.build.json", "clean": "rimraf dist/", "copy-files": "copyfiles -u 1 \"./src/**/*.ejs\" dist/", - "update-permission": "chmod +x ../../node_modules/.bin/ldo", - "watch": "tsc --watch", + "update-permission": "chmod +x ./dist/index.js", "test": "jest --coverage", - "test:watch": "jest --watch", "prepublishOnly": "npm run test && npm run build", "lint": "eslint src/** --fix --no-error-on-unmatched-pattern", - "build:ldo": "./dist/index.js build --input test/.shapes --output test/.ldo" + "test:init": "rm -rf ./example-init && cp -R ./example-init-placeholder ./example-init && cd ./example-init && ../dist/index.js init", + "test:create": "rm -rf ./example-create && ./dist/index.js create ./example-create" }, "repository": { "type": "git", @@ -33,17 +29,21 @@ }, "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/cli#readme", "devDependencies": { + "@ldo/cli": "^0.0.1-alpha.28", "@types/child-process-promise": "^2.2.2", "@types/ejs": "^3.1.1", "@types/fs-extra": "^9.0.13", "@types/jest": "^27.0.3", - "@types/shexj": "2.1.4", + "@types/jsonld": "^1.5.15", + "@types/prompts": "^2.4.9", + "@types/shexj": "^2.1.4", "copyfiles": "^2.4.1", "jest": "^27.4.2", "rimraf": "^3.0.2", "ts-jest": "^27.0.7" }, "dependencies": { + "@ldo/ldo": "^0.0.1-alpha.28", "@ldo/schema-converter-shex": "^0.0.1-alpha.24", "@shexjs/parser": "^1.0.0-alpha.24", "child-process-promise": "^2.2.1", @@ -51,7 +51,10 @@ "ejs": "^3.1.8", "fs-extra": "^10.1.0", "loading-cli": "^1.1.0", - "prettier": "^3.0.3" + "prettier": "^3.0.3", + "prompts": "^2.4.2", + "ts-morph": "^24.0.0", + "type-fest": "^2.19.0" }, "files": [ "dist", diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 0aebb7f..ab81b6e 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -6,6 +6,7 @@ import schemaConverterShex from "@ldo/schema-converter-shex"; import { renderFile } from "ejs"; import prettier from "prettier"; import loading from "loading-cli"; +import { forAllShapes } from "./util/forAllShapes"; interface BuildOptions { input: string; @@ -15,13 +16,6 @@ interface BuildOptions { export async function build(options: BuildOptions) { const load = loading("Peparing Environment"); load.start(); - const shapeDir = await fs.promises.readdir(options.input, { - withFileTypes: true, - }); - // Filter out non-shex documents - const shexFiles = shapeDir.filter( - (file) => file.isFile() && file.name.endsWith(".shex"), - ); // Prepare new folder by clearing/and/or creating it if (fs.existsSync(options.output)) { await fs.promises.rm(options.output, { recursive: true }); @@ -29,51 +23,44 @@ export async function build(options: BuildOptions) { await fs.promises.mkdir(options.output); load.text = "Generating LDO Documents"; - await Promise.all( - shexFiles.map(async (file) => { - const fileName = path.parse(file.name).name; - // Get the content of each document - const shexC = await fs.promises.readFile( - path.join(options.input, file.name), - "utf8", - ); - // Convert to ShexJ - let schema: Schema; - try { - schema = parser.construct("https://ldo.js.org/").parse(shexC); - } catch (err) { - const errMessage = - err instanceof Error - ? err.message - : typeof err === "string" - ? err - : "Unknown Error"; - console.error(`Error processing ${file.name}: ${errMessage}`); - return; - } - // Convert the content to types - const [typings, context] = await schemaConverterShex(schema); - await Promise.all( - ["context", "schema", "shapeTypes", "typings"].map( - async (templateName) => { - const finalContent = await renderFile( - path.join(__dirname, "./templates", `${templateName}.ejs`), - { - typings: typings.typings, - fileName, - schema: JSON.stringify(schema, null, 2), - context: JSON.stringify(context, null, 2), - }, - ); - // Save conversion to document - await fs.promises.writeFile( - path.join(options.output, `${fileName}.${templateName}.ts`), - await prettier.format(finalContent, { parser: "typescript" }), - ); - }, - ), - ); - }), - ); + await forAllShapes(options.input, async (fileName, shexC) => { + // Convert to ShexJ + let schema: Schema; + try { + schema = parser.construct("https://ldo.js.org/").parse(shexC); + } catch (err) { + const errMessage = + err instanceof Error + ? err.message + : typeof err === "string" + ? err + : "Unknown Error"; + console.error(`Error processing ${fileName}: ${errMessage}`); + return; + } + // Convert the content to types + const [typings, context] = await schemaConverterShex(schema); + await Promise.all( + ["context", "schema", "shapeTypes", "typings"].map( + async (templateName) => { + const finalContent = await renderFile( + path.join(__dirname, "./templates", `${templateName}.ejs`), + { + typings: typings.typings, + fileName, + schema: JSON.stringify(schema, null, 2), + context: JSON.stringify(context, null, 2), + }, + ); + // Save conversion to document + await fs.promises.writeFile( + path.join(options.output, `${fileName}.${templateName}.ts`), + await prettier.format(finalContent, { parser: "typescript" }), + ); + }, + ), + ); + }); + load.stop(); } diff --git a/packages/cli/src/create.ts b/packages/cli/src/create.ts new file mode 100644 index 0000000..8f04a1e --- /dev/null +++ b/packages/cli/src/create.ts @@ -0,0 +1,118 @@ +import { init } from "./init"; +import { modifyPackageJson, savePackageJson } from "./util/modifyPackageJson"; +import { generateReadme } from "./generateReadme"; +import path from "path"; +import prompts from "prompts"; +import type { PackageJson } from "type-fest"; +import loading from "loading-cli"; +import { promises as fs } from "fs"; +import { renderFile } from "ejs"; + +export async function create(directory: string) { + // Init the NPM Package + const responses = await prompts([ + { + type: "text", + name: "name", + message: "Package name:", + initial: path.basename(directory), + }, + { + type: "text", + name: "version", + message: "Version:", + initial: "1.0.0", + }, + { + type: "text", + name: "description", + message: "Description:", + }, + { + type: "list", + name: "keywords", + message: "Keywords (comma separated):", + separator: ",", + }, + { + type: "text", + name: "author", + message: "Author:", + }, + { + type: "text", + name: "license", + message: "License:", + initial: "MIT", + }, + { + type: "text", + name: "repository", + message: "Git repository (optional):", + }, + ]); + + const load = loading("Generating package.json"); + + const packageJson: PackageJson = { + name: responses.name, + version: responses.version, + description: responses.description, + keywords: responses.keywords, + author: responses.author, + license: responses.license, + main: "./index.js", + }; + + if (responses.repository) { + packageJson.repository = { + type: "git", + url: responses.repository, + }; + packageJson.bugs = { + url: `${responses.repository.replace(/\.git$/, "")}/issues`, + }; + packageJson.homepage = `${responses.repository.replace( + /\.git$/, + "", + )}#readme`; + } + + await savePackageJson(directory, packageJson); + + // Init LDO + load.text = "Initializing LDO"; + await init(directory); + + // Add prepublish script + await modifyPackageJson(directory, async (packageJson) => { + if (!packageJson.scripts) packageJson.scripts = {}; + packageJson.scripts.prepublish = + "npm run build:ldo & npm run generate-readme"; + packageJson.scripts[ + "genenerate-readme" + ] = `ldo generate-readme --project ./ --shapes ./.shapes --ldo ./.ldo`; + return packageJson; + }); + + // Create index.js + load.text = "Generating index.js"; + const ldoDir = await fs.readdir(path.join(directory, "./.ldo"), { + withFileTypes: true, + }); + const indexText = await renderFile( + path.join(__dirname, "./templates/readme/projectIndex.ejs"), + { fileNames: ldoDir.map((file) => file.name) }, + ); + await fs.writeFile(path.join(directory, "index.js"), indexText); + + // Generate ReadMe + load.text = "Generating README"; + await generateReadme({ + project: directory, + shapes: path.join(directory, ".shapes"), + ldo: path.join(directory, ".ldo"), + }); + + load.stop(); +} diff --git a/packages/cli/src/generateReadme.ts b/packages/cli/src/generateReadme.ts new file mode 100644 index 0000000..c0df82f --- /dev/null +++ b/packages/cli/src/generateReadme.ts @@ -0,0 +1,81 @@ +import { getPackageJson } from "./util/modifyPackageJson"; +import { forAllShapes } from "./util/forAllShapes"; +import { promises as fs } from "fs"; +import path from "path"; +import { Project } from "ts-morph"; +import { renderFile } from "ejs"; + +interface GenerateReadmeOptions { + project: string; + shapes: string; + ldo: string; +} + +interface ReadmeEjsOptions { + projectName: string; + projectDescription: string; + shapes: { + name: string; + types: { + typeName: string; + shapeTypeName: string; + }[]; + shex: string; + typescript: string; + }[]; +} + +export async function generateReadme(options: GenerateReadmeOptions) { + const packageJson = await getPackageJson(options.project); + const projectName = packageJson.name!; + const projectDescription = packageJson.description!; + const shapes: ReadmeEjsOptions["shapes"] = []; + + await forAllShapes(options.shapes, async (fileName, shexC) => { + const typeFilePath = path.join(options.ldo, `${fileName}.typings.ts`); + + const typesRaw = await fs.readFile(typeFilePath, "utf8"); + + const shape: ReadmeEjsOptions["shapes"][0] = { + name: fileName, + shex: shexC, + typescript: typesRaw, + types: [], + }; + + listInterfaces(typeFilePath).forEach((interfaceName) => { + shape.types.push({ + typeName: interfaceName, + shapeTypeName: `${interfaceName}ShapeType`, + }); + }); + + shapes.push(shape); + }); + + const readmeEjsOptions: ReadmeEjsOptions = { + projectName, + projectDescription, + shapes, + }; + + // Save Readme + const finalContent = await renderFile( + path.join(__dirname, "./templates/readme/", "main.ejs"), + readmeEjsOptions, + ); + // Save readme to document + await fs.writeFile(path.join(options.project, "README.md"), finalContent); +} + +/** + * Helper Function that lists all the interfaces in a typescript file + */ +function listInterfaces(filePath: string): string[] { + const project = new Project(); + const sourceFile = project.addSourceFileAtPath(filePath); + + // Get all interfaces in the file + const interfaces = sourceFile.getInterfaces().map((iface) => iface.getName()); + return interfaces; +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c7eb26b..31e96ae 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3,6 +3,7 @@ import { program } from "commander"; import { build } from "./build"; import { init } from "./init"; +import { create } from "./create"; program .name("LDO-CLI") @@ -18,8 +19,34 @@ program program .command("init") - .option("-d --directory>", "A parent directory for ldo files") + .argument("[directory]", "A parent directory for ldo files") .description("Initializes a project for LDO.") .action(init); +program + .command("create") + .argument("", "The package's directory") + .description("Creates a standalone package for shapes to publish to NPM.") + .action(create); + +program + .command("generate-readme") + .description("Create a ReadMe from the shapes and generated code.") + .requiredOption( + "-r, --readme ", + "Provide the path to the readme", + "./.shapes", + ) + .requiredOption( + "-s, --shapes ", + "Provide the path to the shapes folder", + "./.shapes", + ) + .requiredOption( + "-s, --ldo ", + "Provide the path to the ldo folder", + "./.ldo", + ) + .action(build); + program.parse(); diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index d1ffb91..1d08ff4 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -2,22 +2,15 @@ import { exec } from "child-process-promise"; import fs from "fs-extra"; import path from "path"; import { renderFile } from "ejs"; +import { modifyPackageJson } from "./util/modifyPackageJson"; const DEFAULT_SHAPES_FOLDER = "./.shapes"; const DEFAULT_LDO_FOLDER = "./.ldo"; const POTENTIAL_PARENT_DIRECTORIES = ["src", "lib", "bin"]; -export interface InitOptions { - directory?: string; -} - -export async function init(initOptions: InitOptions) { - // Install dependencies - await exec("npm install @ldo/ldo --save"); - await exec("npm install @ldo/cli @types/shexj @types/jsonld --save-dev"); - +export async function init(directory?: string) { // Find folder to save to - let parentDirectory = initOptions.directory; + let parentDirectory = directory!; if (!parentDirectory) { parentDirectory = "./"; const allDirectories = ( @@ -37,6 +30,12 @@ export async function init(initOptions: InitOptions) { } } + // Install dependencies + await exec(`cd ${parentDirectory} && npm install @ldo/ldo --save`); + await exec( + `cd ${parentDirectory} && npm install @ldo/cli @types/shexj @types/jsonld --save-dev`, + ); + // Create "shapes" folder const shapesFolderPath = path.join(parentDirectory, DEFAULT_SHAPES_FOLDER); await fs.promises.mkdir(shapesFolderPath); @@ -57,22 +56,18 @@ export async function init(initOptions: InitOptions) { ); // Add build script - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const packageJson: any = JSON.parse( - (await fs.promises.readFile("./package.json")).toString(), - ); - if (!packageJson.scripts) { - packageJson.scripts = {}; - } - const ldoFolder = path.join(parentDirectory, DEFAULT_LDO_FOLDER); - packageJson.scripts[ - "build:ldo" - ] = `ldo build --input ${shapesFolderPath} --output ${ldoFolder}`; - await fs.promises.writeFile( - "./package.json", - JSON.stringify(packageJson, null, 2), - ); + await modifyPackageJson(parentDirectory, async (packageJson) => { + if (!packageJson.scripts) { + packageJson.scripts = {}; + } + const ldoFolder = path.join(parentDirectory, DEFAULT_LDO_FOLDER); + packageJson.scripts["build:ldo"] = `ldo build --input ${path.relative( + parentDirectory, + shapesFolderPath, + )} --output ${path.relative(parentDirectory, ldoFolder)}`; + return packageJson; + }); // Build LDO - await exec("npm run build:ldo"); + await exec(`cd ${parentDirectory} && npm run build:ldo`); } diff --git a/packages/cli/src/templates/readme/main.ejs b/packages/cli/src/templates/readme/main.ejs new file mode 100644 index 0000000..1ce6681 --- /dev/null +++ b/packages/cli/src/templates/readme/main.ejs @@ -0,0 +1,15 @@ +# <%= projectName %> + +<%- projectDescription %> + +This project includes shapes and generated files for [LDO](https://ldo.js.org). + +## Installation + +```bash +npm i <%= projectName %> +``` + +<% shapes.forEach(function(shape) { %> +<%- include('shape', { shape: shape, projectName: projectName }) %> +<% }); %> \ No newline at end of file diff --git a/packages/cli/src/templates/readme/projectIndex.ejs b/packages/cli/src/templates/readme/projectIndex.ejs new file mode 100644 index 0000000..4f844eb --- /dev/null +++ b/packages/cli/src/templates/readme/projectIndex.ejs @@ -0,0 +1,2 @@ +<% fileNames.forEach((fileName) => { %>export * from "./.ldo/<%- fileName %>"; +<% }); %> \ No newline at end of file diff --git a/packages/cli/src/templates/readme/shape.ejs b/packages/cli/src/templates/readme/shape.ejs new file mode 100644 index 0000000..9973581 --- /dev/null +++ b/packages/cli/src/templates/readme/shape.ejs @@ -0,0 +1,27 @@ +## <%= shape.name %> + +### Usage with LDO + +```typescript +import { createLdoDataset } from "@ldo/ldo"; +import { <%= shape.types.map((type) => type.shapeTypeName).join(", ") %> } from "<%= projectName %>"; +import type { <%= shape.types.map((type) => type.typeName).join(", ") %> } from "<%= projectName %>"; +const ldoDataset = createLdoDataset(); +<% shape.types.forEach(function(type, index) { %> +const example<%= index %>: <%= type.typeName %> = ldoDataset + .usingType(<%= type.shapeTypeName %>) + .fromSubject("http://example.com/example<%= index %>"); +<% }); %> +``` + +### ShEx Typings + +```shex +<%- shape.shex %> +``` + +### TypeScript Typings + +```typescript +<%- shape.typescript %> +``` diff --git a/packages/cli/src/util/forAllShapes.ts b/packages/cli/src/util/forAllShapes.ts new file mode 100644 index 0000000..7897b35 --- /dev/null +++ b/packages/cli/src/util/forAllShapes.ts @@ -0,0 +1,26 @@ +import fs from "fs"; +import path from "path"; + +export async function forAllShapes( + shapePath: string, + callback: (filename: string, shape: string) => Promise, +): Promise { + const shapeDir = await fs.promises.readdir(shapePath, { + withFileTypes: true, + }); + // Filter out non-shex documents + const shexFiles = shapeDir.filter( + (file) => file.isFile() && file.name.endsWith(".shex"), + ); + await Promise.all( + shexFiles.map(async (file) => { + const fileName = path.parse(file.name).name; + // Get the content of each document + const shexC = await fs.promises.readFile( + path.join(shapePath, file.name), + "utf8", + ); + await callback(fileName, shexC); + }), + ); +} diff --git a/packages/cli/src/util/modifyPackageJson.ts b/packages/cli/src/util/modifyPackageJson.ts new file mode 100644 index 0000000..2bc0d54 --- /dev/null +++ b/packages/cli/src/util/modifyPackageJson.ts @@ -0,0 +1,33 @@ +import type { PackageJson } from "type-fest"; +import fs from "fs-extra"; +import path from "path"; + +export async function getPackageJson( + projectFolder: string, +): Promise { + return JSON.parse( + ( + await fs.promises.readFile(path.join(projectFolder, "./package.json")) + ).toString(), + ); +} + +export async function savePackageJson( + projectFolder: string, + packageJson: PackageJson, +): Promise { + await fs.promises.mkdir(projectFolder, { recursive: true }); + await fs.promises.writeFile( + path.join(projectFolder, "./package.json"), + JSON.stringify(packageJson, null, 2), + ); +} + +export async function modifyPackageJson( + projectFolder: string, + modifyCallback: (packageJson: PackageJson) => Promise, +): Promise { + const packageJson: PackageJson = await getPackageJson(projectFolder); + const newPackageJson = await modifyCallback(packageJson); + await savePackageJson(projectFolder, newPackageJson); +} diff --git a/packages/cli/test/.shapes/foafProfile.shex b/packages/cli/test/.shapes/foafProfile.shex deleted file mode 100644 index 95c472b..0000000 --- a/packages/cli/test/.shapes/foafProfile.shex +++ /dev/null @@ -1,19 +0,0 @@ -# This shape is provided by default as an example -# You can create your own shape to fit your needs using ShEx (https://shex.io) -# Also check out https://shaperepo.com for examples of more shapes. - -PREFIX ex: -PREFIX foaf: -PREFIX rdfs: -PREFIX xsd: - -ex:FoafProfile EXTRA a { - a [ foaf:Person ] - // rdfs:comment "Defines the node as a Person (from foaf)" ; - foaf:name xsd:string ? - // rdfs:comment "Define a person's name." ; - foaf:img xsd:string ? - // rdfs:comment "Photo link but in string form" - foaf:knows @ex:FoafProfile * - // rdfs:comment "A list of WebIds for all the people this user knows." ; -} diff --git a/packages/cli/test/placeholder.test.ts b/packages/cli/test/placeholder.test.ts deleted file mode 100644 index 4cc4fd7..0000000 --- a/packages/cli/test/placeholder.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -it("placeholder", () => { - expect(true).toBe(true); -});