Merge pull request #64 from o-development/feat/auto-generator

Feat/auto generator
main
jaxoncreed 9 months ago committed by GitHub
commit 30b9a4b15d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 129
      package-lock.json
  2. 2
      packages/cli/.gitignore
  3. 11
      packages/cli/README.md
  4. 11
      packages/cli/example-init-placeholder/package.json
  5. 23
      packages/cli/package.json
  6. 93
      packages/cli/src/build.ts
  7. 118
      packages/cli/src/create.ts
  8. 81
      packages/cli/src/generateReadme.ts
  9. 29
      packages/cli/src/index.ts
  10. 47
      packages/cli/src/init.ts
  11. 15
      packages/cli/src/templates/readme/main.ejs
  12. 2
      packages/cli/src/templates/readme/projectIndex.ejs
  13. 27
      packages/cli/src/templates/readme/shape.ejs
  14. 26
      packages/cli/src/util/forAllShapes.ts
  15. 33
      packages/cli/src/util/modifyPackageJson.ts
  16. 19
      packages/cli/test/.shapes/foafProfile.shex
  17. 3
      packages/cli/test/placeholder.test.ts

129
package-lock.json generated

@ -8093,6 +8093,32 @@
"@ts-jison/lexer": "^0.4.1-alpha.1" "@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": { "node_modules/@tsconfig/node10": {
"version": "1.0.9", "version": "1.0.9",
"devOptional": true, "devOptional": true,
@ -8453,7 +8479,9 @@
} }
}, },
"node_modules/@types/jsonld": { "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" "license": "MIT"
}, },
"node_modules/@types/keygrip": { "node_modules/@types/keygrip": {
@ -8569,6 +8597,17 @@
"version": "2.7.3", "version": "2.7.3",
"license": "MIT" "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": { "node_modules/@types/prop-types": {
"version": "15.7.11", "version": "15.7.11",
"dev": true, "dev": true,
@ -8713,6 +8752,8 @@
}, },
"node_modules/@types/shexj": { "node_modules/@types/shexj": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/shexj/-/shexj-2.1.4.tgz",
"integrity": "sha512-/dcF8vT/CHseZxNTcWR+otf6018PACgHiKFukPYsxQCRppGZq0UcALMedZUUnj7IM4WOesFoERwJBhEw44d/VQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -11031,6 +11072,12 @@
"node": ">=4" "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": { "node_modules/codepage": {
"version": "1.15.0", "version": "1.15.0",
"dev": true, "dev": true,
@ -21261,6 +21308,12 @@
"tslib": "^2.0.3" "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": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"license": "MIT", "license": "MIT",
@ -22759,6 +22812,8 @@
}, },
"node_modules/prompts": { "node_modules/prompts": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"kleur": "^3.0.3", "kleur": "^3.0.3",
@ -26605,6 +26660,45 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/tmp": {
"version": "0.2.1", "version": "0.2.1",
"dev": true, "dev": true,
@ -26713,6 +26807,16 @@
"version": "0.1.13", "version": "0.1.13",
"license": "Apache-2.0" "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": { "node_modules/ts-node": {
"version": "10.9.2", "version": "10.9.2",
"devOptional": true, "devOptional": true,
@ -28640,6 +28744,7 @@
"version": "0.0.1-alpha.28", "version": "0.0.1-alpha.28",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/schema-converter-shex": "^0.0.1-alpha.24", "@ldo/schema-converter-shex": "^0.0.1-alpha.24",
"@shexjs/parser": "^1.0.0-alpha.24", "@shexjs/parser": "^1.0.0-alpha.24",
"child-process-promise": "^2.2.1", "child-process-promise": "^2.2.1",
@ -28647,17 +28752,23 @@
"ejs": "^3.1.8", "ejs": "^3.1.8",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"loading-cli": "^1.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": { "bin": {
"ldo": "dist/index.js" "ldo": "dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@ldo/cli": "^0.0.1-alpha.28",
"@types/child-process-promise": "^2.2.2", "@types/child-process-promise": "^2.2.2",
"@types/ejs": "^3.1.1", "@types/ejs": "^3.1.1",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/jest": "^27.0.3", "@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", "copyfiles": "^2.4.1",
"jest": "^27.4.2", "jest": "^27.4.2",
"rimraf": "^3.0.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": { "packages/cli/node_modules/typescript": {
"version": "4.9.5", "version": "4.9.5",
"dev": true, "dev": true,

@ -0,0 +1,2 @@
example-create
example-init

@ -53,9 +53,20 @@ This will generate five files:
- `./ldo/foafProfile.schema.ts` - `./ldo/foafProfile.schema.ts`
- `./ldo/foafProfile.context.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 ## API Details
- [`init` command](https://ldo.js.org/api/cli/init/) - [`init` command](https://ldo.js.org/api/cli/init/)
- [`build` command](https://ldo.js.org/api/cli/build/) - [`build` command](https://ldo.js.org/api/cli/build/)
- [`create` command](https://ldo.js.org/api/cli/create/)
``` ```
## Sponsorship ## Sponsorship

@ -0,0 +1,11 @@
{
"name": "example-init",
"version": "1.0.0",
"description": "",
"keywords": [
""
],
"author": "",
"license": "MIT",
"main": "./index.js"
}

@ -7,20 +7,16 @@
"ldo": "./dist/index.js" "ldo": "./dist/index.js"
}, },
"scripts": { "scripts": {
"start": "node dist/index.js build", "build": "npm run clean && npm run build:ts && npm run copy-files && npm run update-permission",
"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:ts": "tsc --project tsconfig.build.json", "build:ts": "tsc --project tsconfig.build.json",
"clean": "rimraf dist/", "clean": "rimraf dist/",
"copy-files": "copyfiles -u 1 \"./src/**/*.ejs\" dist/", "copy-files": "copyfiles -u 1 \"./src/**/*.ejs\" dist/",
"update-permission": "chmod +x ../../node_modules/.bin/ldo", "update-permission": "chmod +x ./dist/index.js",
"watch": "tsc --watch",
"test": "jest --coverage", "test": "jest --coverage",
"test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build", "prepublishOnly": "npm run test && npm run build",
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern", "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": { "repository": {
"type": "git", "type": "git",
@ -33,17 +29,21 @@
}, },
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/cli#readme", "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/cli#readme",
"devDependencies": { "devDependencies": {
"@ldo/cli": "^0.0.1-alpha.28",
"@types/child-process-promise": "^2.2.2", "@types/child-process-promise": "^2.2.2",
"@types/ejs": "^3.1.1", "@types/ejs": "^3.1.1",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/jest": "^27.0.3", "@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", "copyfiles": "^2.4.1",
"jest": "^27.4.2", "jest": "^27.4.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-jest": "^27.0.7" "ts-jest": "^27.0.7"
}, },
"dependencies": { "dependencies": {
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/schema-converter-shex": "^0.0.1-alpha.24", "@ldo/schema-converter-shex": "^0.0.1-alpha.24",
"@shexjs/parser": "^1.0.0-alpha.24", "@shexjs/parser": "^1.0.0-alpha.24",
"child-process-promise": "^2.2.1", "child-process-promise": "^2.2.1",
@ -51,7 +51,10 @@
"ejs": "^3.1.8", "ejs": "^3.1.8",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"loading-cli": "^1.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": [ "files": [
"dist", "dist",

@ -6,6 +6,7 @@ import schemaConverterShex from "@ldo/schema-converter-shex";
import { renderFile } from "ejs"; import { renderFile } from "ejs";
import prettier from "prettier"; import prettier from "prettier";
import loading from "loading-cli"; import loading from "loading-cli";
import { forAllShapes } from "./util/forAllShapes";
interface BuildOptions { interface BuildOptions {
input: string; input: string;
@ -15,13 +16,6 @@ interface BuildOptions {
export async function build(options: BuildOptions) { export async function build(options: BuildOptions) {
const load = loading("Peparing Environment"); const load = loading("Peparing Environment");
load.start(); 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 // Prepare new folder by clearing/and/or creating it
if (fs.existsSync(options.output)) { if (fs.existsSync(options.output)) {
await fs.promises.rm(options.output, { recursive: true }); await fs.promises.rm(options.output, { recursive: true });
@ -29,51 +23,44 @@ export async function build(options: BuildOptions) {
await fs.promises.mkdir(options.output); await fs.promises.mkdir(options.output);
load.text = "Generating LDO Documents"; load.text = "Generating LDO Documents";
await Promise.all( await forAllShapes(options.input, async (fileName, shexC) => {
shexFiles.map(async (file) => { // Convert to ShexJ
const fileName = path.parse(file.name).name; let schema: Schema;
// Get the content of each document try {
const shexC = await fs.promises.readFile( schema = parser.construct("https://ldo.js.org/").parse(shexC);
path.join(options.input, file.name), } catch (err) {
"utf8", const errMessage =
); err instanceof Error
// Convert to ShexJ ? err.message
let schema: Schema; : typeof err === "string"
try { ? err
schema = parser.construct("https://ldo.js.org/").parse(shexC); : "Unknown Error";
} catch (err) { console.error(`Error processing ${fileName}: ${errMessage}`);
const errMessage = return;
err instanceof Error }
? err.message // Convert the content to types
: typeof err === "string" const [typings, context] = await schemaConverterShex(schema);
? err await Promise.all(
: "Unknown Error"; ["context", "schema", "shapeTypes", "typings"].map(
console.error(`Error processing ${file.name}: ${errMessage}`); async (templateName) => {
return; const finalContent = await renderFile(
} path.join(__dirname, "./templates", `${templateName}.ejs`),
// Convert the content to types {
const [typings, context] = await schemaConverterShex(schema); typings: typings.typings,
await Promise.all( fileName,
["context", "schema", "shapeTypes", "typings"].map( schema: JSON.stringify(schema, null, 2),
async (templateName) => { context: JSON.stringify(context, null, 2),
const finalContent = await renderFile( },
path.join(__dirname, "./templates", `${templateName}.ejs`), );
{ // Save conversion to document
typings: typings.typings, await fs.promises.writeFile(
fileName, path.join(options.output, `${fileName}.${templateName}.ts`),
schema: JSON.stringify(schema, null, 2), await prettier.format(finalContent, { parser: "typescript" }),
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(); load.stop();
} }

@ -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();
}

@ -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;
}

@ -3,6 +3,7 @@
import { program } from "commander"; import { program } from "commander";
import { build } from "./build"; import { build } from "./build";
import { init } from "./init"; import { init } from "./init";
import { create } from "./create";
program program
.name("LDO-CLI") .name("LDO-CLI")
@ -18,8 +19,34 @@ program
program program
.command("init") .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.") .description("Initializes a project for LDO.")
.action(init); .action(init);
program
.command("create")
.argument("<directory>", "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 <readmePath>",
"Provide the path to the readme",
"./.shapes",
)
.requiredOption(
"-s, --shapes <shapesPath>",
"Provide the path to the shapes folder",
"./.shapes",
)
.requiredOption(
"-s, --ldo <ldoPath>",
"Provide the path to the ldo folder",
"./.ldo",
)
.action(build);
program.parse(); program.parse();

@ -2,22 +2,15 @@ import { exec } from "child-process-promise";
import fs from "fs-extra"; import fs from "fs-extra";
import path from "path"; import path from "path";
import { renderFile } from "ejs"; import { renderFile } from "ejs";
import { modifyPackageJson } from "./util/modifyPackageJson";
const DEFAULT_SHAPES_FOLDER = "./.shapes"; const DEFAULT_SHAPES_FOLDER = "./.shapes";
const DEFAULT_LDO_FOLDER = "./.ldo"; const DEFAULT_LDO_FOLDER = "./.ldo";
const POTENTIAL_PARENT_DIRECTORIES = ["src", "lib", "bin"]; const POTENTIAL_PARENT_DIRECTORIES = ["src", "lib", "bin"];
export interface InitOptions { export async function init(directory?: string) {
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");
// Find folder to save to // Find folder to save to
let parentDirectory = initOptions.directory; let parentDirectory = directory!;
if (!parentDirectory) { if (!parentDirectory) {
parentDirectory = "./"; parentDirectory = "./";
const allDirectories = ( 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 // Create "shapes" folder
const shapesFolderPath = path.join(parentDirectory, DEFAULT_SHAPES_FOLDER); const shapesFolderPath = path.join(parentDirectory, DEFAULT_SHAPES_FOLDER);
await fs.promises.mkdir(shapesFolderPath); await fs.promises.mkdir(shapesFolderPath);
@ -57,22 +56,18 @@ export async function init(initOptions: InitOptions) {
); );
// Add build script // Add build script
// eslint-disable-next-line @typescript-eslint/no-explicit-any await modifyPackageJson(parentDirectory, async (packageJson) => {
const packageJson: any = JSON.parse( if (!packageJson.scripts) {
(await fs.promises.readFile("./package.json")).toString(), packageJson.scripts = {};
); }
if (!packageJson.scripts) { const ldoFolder = path.join(parentDirectory, DEFAULT_LDO_FOLDER);
packageJson.scripts = {}; packageJson.scripts["build:ldo"] = `ldo build --input ${path.relative(
} parentDirectory,
const ldoFolder = path.join(parentDirectory, DEFAULT_LDO_FOLDER); shapesFolderPath,
packageJson.scripts[ )} --output ${path.relative(parentDirectory, ldoFolder)}`;
"build:ldo" return packageJson;
] = `ldo build --input ${shapesFolderPath} --output ${ldoFolder}`; });
await fs.promises.writeFile(
"./package.json",
JSON.stringify(packageJson, null, 2),
);
// Build LDO // Build LDO
await exec("npm run build:ldo"); await exec(`cd ${parentDirectory} && npm run build:ldo`);
} }

@ -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 }) %>
<% }); %>

@ -0,0 +1,2 @@
<% fileNames.forEach((fileName) => { %>export * from "./.ldo/<%- fileName %>";
<% }); %>

@ -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 %>
```

@ -0,0 +1,26 @@
import fs from "fs";
import path from "path";
export async function forAllShapes(
shapePath: string,
callback: (filename: string, shape: string) => Promise<void>,
): Promise<void> {
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);
}),
);
}

@ -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<PackageJson> {
return JSON.parse(
(
await fs.promises.readFile(path.join(projectFolder, "./package.json"))
).toString(),
);
}
export async function savePackageJson(
projectFolder: string,
packageJson: PackageJson,
): Promise<void> {
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<PackageJson>,
): Promise<void> {
const packageJson: PackageJson = await getPackageJson(projectFolder);
const newPackageJson = await modifyCallback(packageJson);
await savePackageJson(projectFolder, newPackageJson);
}

@ -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: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
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." ;
}

@ -1,3 +0,0 @@
it("placeholder", () => {
expect(true).toBe(true);
});
Loading…
Cancel
Save