From 02a591c4d191cd2145d3b20b0eab7d09cc62175a Mon Sep 17 00:00:00 2001 From: rido-min Date: Sun, 11 Oct 2020 19:55:41 -0700 Subject: [PATCH 01/14] sync from master --- test/badpath.json | 14 +++++++++++ test/repo-convention.test.js | 31 +++++++++++++++++++++++ test/test-models.js | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 test/badpath.json create mode 100644 test/repo-convention.test.js create mode 100644 test/test-models.js diff --git a/test/badpath.json b/test/badpath.json new file mode 100644 index 0000000..4d4f969 --- /dev/null +++ b/test/badpath.json @@ -0,0 +1,14 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:test:onedep;1", + "@type": "Interface", + "displayName": "onedep", + "extends": ["dtmi:test:uniqueids;1"], + "contents": [ + { + "@type": "Component", + "name": "temperature", + "schema": "dtmi:test:onedep:comp1;1" + } + ] +} diff --git a/test/repo-convention.test.js b/test/repo-convention.test.js new file mode 100644 index 0000000..1e9dec5 --- /dev/null +++ b/test/repo-convention.test.js @@ -0,0 +1,31 @@ +const rc = require('../repo-convention.js') +const td = require('./test-models') + +test('invalid dtmi', () => { + expect(rc.dtmiToPath('')).toBe(null) + expect(rc.dtmiToPath('notadtmi')).toBe(null) + expect(rc.dtmiToPath('dtmi:notadtmi')).toBe(null) + expect(rc.dtmiToPath('dtmi:com:example:thermostat:1')).toBe(null) + expect(rc.dtmiToPath('dtmi:com:example-bad:thermostat;1')).toBe(null) +}) + +test('dtmi to path', () => { + expect(rc.dtmiToPath('dtmi:com:example:Thermostat;1')).toBe('/dtmi/com/example/thermostat-1.json') + expect(rc.dtmiToPath('dtmi:com:Example:thermostat;1')).toBe('/dtmi/com/example/thermostat-1.json') +}) + +test('get dependencies', () => { + expect(rc.getDependencies(td.noDepsJson)).toEqual([]) + expect(rc.getDependencies(td.oneDepJson)).toEqual(['dtmi:test:base;1']) + expect(rc.getDependencies(td.twoDepsJson)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) +}) + +test('check ids', () => { + expect(rc.checkIds(td.globalId)).toBe(false) + expect(rc.checkIds(td.oneDepJson)).toBe(true) +}) + +test('checkDtmiPathFromFile', () => { + expect(rc.checkDtmiPathFromFile('../dtmi/test/onedep-1.json')).toBe(true) + expect(rc.checkDtmiPathFromFile('badpath.json')).toBe(false) +}) diff --git a/test/test-models.js b/test/test-models.js new file mode 100644 index 0000000..4b870bc --- /dev/null +++ b/test/test-models.js @@ -0,0 +1,49 @@ +const noDepsJson = { + '@context': 'dtmi:dtdl:context;2', + '@id': 'dtmi:test:onedep;1', + '@type': 'Interface', + displayName: 'onedep', + contents: [] +} + +const oneDepJson = { + '@context': 'dtmi:dtdl:context;2', + '@id': 'dtmi:test:onedep;1', + '@type': 'Interface', + displayName: 'onedep', + extends: 'dtmi:test:base;1', + contents: [] +} + +const globalId = { + '@context': 'dtmi:dtdl:context;2', + '@id': 'dtmi:test:twodeps;1', + '@type': 'Interface', + displayName: 'onedep', + extends: ['dtmi:test:base;1'], + contents: [ + { + '@id': 'dtmi:global;1', + '@type': 'Component', + name: 'temperature', + schema: 'dtmi:test:onedep:comp1;1' + } + ] +} + +const twoDepsJson = { + '@context': 'dtmi:dtdl:context;2', + '@id': 'dtmi:test:twodeps;1', + '@type': 'Interface', + displayName: 'onedep', + extends: ['dtmi:test:base;1'], + contents: [ + { + '@type': 'Component', + name: 'temperature', + schema: 'dtmi:test:onedep:comp1;1' + } + ] +} + +module.exports = { noDepsJson, oneDepJson, twoDepsJson, globalId } From 56ed8faac525b5b29bfcdb14cd272227b41e0d62 Mon Sep 17 00:00:00 2001 From: rido-min Date: Sun, 11 Oct 2020 19:56:26 -0700 Subject: [PATCH 02/14] sync from prod --- .github/ISSUE_TEMPLATE/code_owner_request.md | 22 +++++ .../ISSUE_TEMPLATE/model_edit_or_removal.md | 16 ++++ .../pull_request_template.md | 19 ++++ .../workflows/push_expanded_to_storage.yml | 47 ++++++++++ .github/workflows/push_to_storage.yml | 30 ++++++ .github/workflows/validate-deps.js | 18 ++++ .github/workflows/validate-files.js | 13 +++ .github/workflows/validate-files.yml | 29 ++++++ .github/workflows/validate-ids.js | 16 ++++ .github/workflows/validate-models.yml | 44 +++++++++ add-model.js | 51 ++++++++++ dtmi2path.js | 14 --- repo-convention.js | 93 ++++++++++++++----- repo-convention.test.js | 35 ------- 14 files changed, 377 insertions(+), 70 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/code_owner_request.md create mode 100644 .github/ISSUE_TEMPLATE/model_edit_or_removal.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md create mode 100644 .github/workflows/push_expanded_to_storage.yml create mode 100644 .github/workflows/push_to_storage.yml create mode 100644 .github/workflows/validate-deps.js create mode 100644 .github/workflows/validate-files.js create mode 100644 .github/workflows/validate-files.yml create mode 100644 .github/workflows/validate-ids.js create mode 100644 .github/workflows/validate-models.yml create mode 100644 add-model.js delete mode 100644 dtmi2path.js delete mode 100644 repo-convention.test.js diff --git a/.github/ISSUE_TEMPLATE/code_owner_request.md b/.github/ISSUE_TEMPLATE/code_owner_request.md new file mode 100644 index 0000000..3374785 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/code_owner_request.md @@ -0,0 +1,22 @@ +--- +name: Code owner request +about: Use this template to request code owner over a particular path +title: Code owner request +labels: code owner +assignees: '' + +--- + +GitHub username(s): + +Repository path(s)/DTMI(s): + +Have you submitted a device model to this path/DTMI (yes/no): + +Username(s) for any existing code owners for this path: + +How many DTDL documents currently exist in this path: + +Justification: + +Is this a request to remove an existing code owner? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/model_edit_or_removal.md b/.github/ISSUE_TEMPLATE/model_edit_or_removal.md new file mode 100644 index 0000000..0b4557d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/model_edit_or_removal.md @@ -0,0 +1,16 @@ +--- +name: Model edit or removal request +about: Use this template to request removal of models from this repository +title: Model edit or removal +labels: model removal, model edit +assignees: '' + +--- + +Repository path and DTMI (one issue per file/DTMI): + +Are you a listed CODEOWNER? + +Username(s) for any other code owners for this path: + +Justification: diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..6e516b0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,19 @@ + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +Thank you for contributing to the Device Models repository! + +This checklist is used to make sure that common guidelines for a pull request are followed. + +## General Guidelines + +- [ ] The pull request **only** contains device model documents? +- [ ] All device models are in JSON-LD format? +- [ ] One device model per file? +- [ ] A `@context` property at the root of the DTDL document specifying the DTDL version ("dtmi:dtdl:context;2")? +- [ ] One `@id` property specified at the root of the DTDL document? +- [ ] The root `@id` is a valid DTMI? +- [ ] The file path follows the naming conventions in `./CONTRIBUTING.md`? +- [ ] The file path is lower case? +- [ ] Any sub-Id is namespaced "under" the root DTMI? +- [ ] Any references to DTMIs can be resolved within **this** repository (either as existing documents or included in this pull request)? diff --git a/.github/workflows/push_expanded_to_storage.yml b/.github/workflows/push_expanded_to_storage.yml new file mode 100644 index 0000000..1e1dff8 --- /dev/null +++ b/.github/workflows/push_expanded_to_storage.yml @@ -0,0 +1,47 @@ +name: push_expanded_to_azure_storage + +on: + push: + branches: [main] + paths: + - 'dtmi/**/*.json' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - id: files + uses: jitterbit/get-changed-files@v1 + + - name: Configure GitHub feed + run: dotnet nuget add source https://nuget.pkg.github.com/Azure/index.json -n github -u Azure -p ${{secrets.GITHUB_TOKEN}} --store-password-in-clear-text + + - name: Install resolver client + run: dotnet tool install -g dmr-client --add-source https://nuget.pkg.github.com/Azure/index.json + + - name: Expand models + run: | + for changed_file in ${{ steps.files.outputs.added_modified }}; do + if [[ ${changed_file} =~ ^dtmi([\/\\][a-z0-9_]+){2,}-([1-9][0-9]{0,8}).json$ ]]; then + dmr-client resolve --model-file ${changed_file} --repository $PWD -o ${changed_file//.json/.expanded.json} --silent + fi + done + shell: bash + + - name: CLI push files + uses: azure/CLI@v1 + with: + inlineScript: | + for changed_file in ${{ steps.files.outputs.added_modified }}; do + if [[ ${changed_file} =~ ^dtmi([\/\\][a-z0-9_]+){2,}-([1-9][0-9]{0,8}).json$ ]]; then + expanded_file=${changed_file//.json/.expanded.json} + + echo "Submitting ${expanded_file} to Azure Storage" + az storage blob upload --connection-string "${{ secrets.STORAGE_CONNECTION_STRING }}" --container-name '$web' --name "${expanded_file}" --file "${expanded_file}" --no-progress + else + echo "Skipping ${changed_file}. It doesn't appear to be a DTDL document." + fi + done diff --git a/.github/workflows/push_to_storage.yml b/.github/workflows/push_to_storage.yml new file mode 100644 index 0000000..6bf2cad --- /dev/null +++ b/.github/workflows/push_to_storage.yml @@ -0,0 +1,30 @@ +name: push_to_azure_storage + +on: + push: + branches: [main] + paths: + - 'dtmi/**/*.json' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - id: files + uses: jitterbit/get-changed-files@v1 + + - name: CLI push files + uses: azure/CLI@v1 + with: + inlineScript: | + for changed_file in ${{ steps.files.outputs.added_modified }}; do + if [[ ${changed_file} =~ ^dtmi([\/\\][a-z0-9_]+){2,}-([1-9][0-9]{0,8}).json$ ]]; then + echo "Submitting ${changed_file} to Azure Storage" + az storage blob upload --connection-string "${{ secrets.STORAGE_CONNECTION_STRING }}" --container-name '$web' --name "${changed_file}" --file "${changed_file}" --no-progress + else + echo "Skipping ${changed_file}. It doesn't appear to be a DTDL document." + fi + done diff --git a/.github/workflows/validate-deps.js b/.github/workflows/validate-deps.js new file mode 100644 index 0000000..29d5865 --- /dev/null +++ b/.github/workflows/validate-deps.js @@ -0,0 +1,18 @@ +const path = require('path') +const fs = require('fs') +const { checkDependencies } = require('../../repo-convention') + +for (let i = 1; i < process.argv.length; i++) { + const file = path.normalize((path.join(process.cwd(), process.argv[i]))) + if (file.startsWith(path.join(process.cwd(), 'dtmi/'))) { + console.log('\nchecking: ' + file) + const dtdlJson = JSON.parse(fs.readFileSync(file, 'utf-8')) + const id = dtdlJson['@id'] + console.log('Scanning dependencies for: ' + id) + if (!checkDependencies(id)) { + process.exit(1) + } + } else { + console.debug('Skipping file: ' + file) + } +} diff --git a/.github/workflows/validate-files.js b/.github/workflows/validate-files.js new file mode 100644 index 0000000..1344271 --- /dev/null +++ b/.github/workflows/validate-files.js @@ -0,0 +1,13 @@ +const path = require('path') +const { checkDtmiPathFromFile } = require('../../repo-convention') + +for (let i = 1; i < process.argv.length; i++) { + const file = path.normalize(process.argv[i]) + const fullFilePath = path.normalize((path.join(process.cwd(), process.argv[i]))) + if (fullFilePath.startsWith(path.join(process.cwd(), 'dtmi/'))) { + console.log('\nchecking: ' + file) + checkDtmiPathFromFile(file) + } else { + console.debug('Skipping file: ' + file) + } +} diff --git a/.github/workflows/validate-files.yml b/.github/workflows/validate-files.yml new file mode 100644 index 0000000..2a2c54f --- /dev/null +++ b/.github/workflows/validate-files.yml @@ -0,0 +1,29 @@ +name: validate-files + +on: + pull_request: + branches: [main] + paths: + - 'dtmi/**' + +jobs: + validate-files: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - id: files + uses: jitterbit/get-changed-files@v1 + + - name: npm install + run: npm install + + - name: check DTMI path convention + run: node .github/workflows/validate-files.js ${{steps.files.outputs.added_modified}} + + - name: check global IDs + run: node .github/workflows/validate-ids.js ${{steps.files.outputs.added_modified}} + + - name: check dependencies + run: node .github/workflows/validate-deps.js ${{steps.files.outputs.added_modified}} diff --git a/.github/workflows/validate-ids.js b/.github/workflows/validate-ids.js new file mode 100644 index 0000000..8367307 --- /dev/null +++ b/.github/workflows/validate-ids.js @@ -0,0 +1,16 @@ +const path = require('path') +const fs = require('fs') +const { checkIds } = require('../../repo-convention') + +for (let i = 2; i < process.argv.length; i++) { + const file = path.normalize((path.join(process.cwd(), process.argv[i]))) + if (file.startsWith(path.join(process.cwd(), 'dtmi/'))) { + console.log('\nchecking: ' + file) + if (!checkIds(JSON.parse(fs.readFileSync(file, 'utf-8')))) { + console.error(`ERROR validating ids on file ${file}. Aborting.`) + process.exit(1) + } + } else { + console.debug('Skipping file: ' + file) + } +} diff --git a/.github/workflows/validate-models.yml b/.github/workflows/validate-models.yml new file mode 100644 index 0000000..1a9e658 --- /dev/null +++ b/.github/workflows/validate-models.yml @@ -0,0 +1,44 @@ +name: validate-models + +on: + pull_request: + branches: [main] + paths: + - 'dtmi/**' + +jobs: + validate-models: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - id: files + uses: jitterbit/get-changed-files@v1 + + - name: Configure GitHub feed + run: dotnet nuget add source https://nuget.pkg.github.com/Azure/index.json -n github -u Azure -p ${{secrets.GITHUB_TOKEN}} --store-password-in-clear-text + + - name: Install resolver client + run: dotnet tool install -g dmr-client --add-source https://nuget.pkg.github.com/Azure/index.json + + - name: Parse Models + run: | + for f in ${{ steps.files.outputs.added_modified }}; + do + if [[ $f == *"dtmi/"* ]] + then + echo "validating $f" + dmr-client validate --model-file $f --repository $PWD + if [ $? -eq 0 ] + then + echo "validation ok: $f" + else + echo "error validating model" + exit 1 + fi + else + echo "skipping $f" + fi + done + shell: bash diff --git a/add-model.js b/add-model.js new file mode 100644 index 0000000..ab39664 --- /dev/null +++ b/add-model.js @@ -0,0 +1,51 @@ +const fs = require('fs') +const path = require('path') +const mkdirp = require('mkdirp') +const { dtmiToPath, checkIds } = require('../repo-convention.js') + +const createInterfaceFromFile = file => { + const jsonDtdl = JSON.parse(fs.readFileSync(file, 'utf-8')) + createInterfaceFromJson(jsonDtdl) +} + +const createInterfaceFromJson = jsonDtdl => { + const dtmi = jsonDtdl['@id'] + const fileName = path.join(process.cwd(), dtmiToPath(dtmi)) + if (fs.existsSync(fileName)) { + console.log(`ERROR: ID ${dtmi} already exists at ${fileName} . Aborting `) + return false + } + mkdirp(path.dirname(fileName)).then(m => { + fs.writeFileSync(fileName, JSON.stringify(jsonDtdl, null, 2)) + console.log(`Model ${dtmi} added successfully to ${fileName}`) + }) +} + +/** + * @description Adds a model to the repo. Validates ids, dependencies and set the right folder/file name + * @param {string} file + */ +const addModel = (file) => { + const rootJson = JSON.parse(fs.readFileSync(file, 'utf-8')) + + if (Array.isArray(rootJson)) { + rootJson.forEach(d => { + checkIds(d) + createInterfaceFromJson(d) + }) + return rootJson[0]['@id'] + } else { + checkIds(rootJson) + createInterfaceFromFile(file) + return rootJson['@id'] + } +} + +const main = () => { + const file = process.argv[2] + console.log(`processing: ${file}`) + const id = addModel(file) + console.log(id) +} + +main() diff --git a/dtmi2path.js b/dtmi2path.js deleted file mode 100644 index 163af98..0000000 --- a/dtmi2path.js +++ /dev/null @@ -1,14 +0,0 @@ -export /** - * @description Converts DTMI to dtmi/com/example/device-1.json path - * @param {string} dtmi - * @returns {string)} - */ -const dtmi2path = dtmi => { - if (RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi)) { - const idAndVersion = dtmi.toLowerCase().split(';') - const ids = idAndVersion[0].split(':') - const fileName = `${ids.pop()}-${idAndVersion[1]}.json` - const modelFolder = ids.join('/') - return `${modelFolder}/${fileName}` - } else return 'NOT-VALID-DTMI' -} diff --git a/repo-convention.js b/repo-convention.js index 8d8aeea..917e440 100644 --- a/repo-convention.js +++ b/repo-convention.js @@ -1,25 +1,46 @@ -import fs from 'fs' -import path from 'path' -import jsonata from 'jsonata' -import { dtmi2path } from './dtmi2path.js' -export { dtmi2path } from './dtmi2path.js' +const fs = require('fs') +const path = require('path') +const jsonata = require('jsonata') -export /** - * @description Returns external IDs in extend and component schemas +/** + * @description Validates DTMI with RegEx from https://github.com/Azure/digital-twin-model-identifier#validation-regular-expressions + * @param {string} dtmi + */ +const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) + +/** + * @description Converts DTMI to /dtmi/com/example/device-1.json path. + * @param {string} dtmi + * @returns {string} + */ +const dtmiToPath = dtmi => { + if (!isDtmi(dtmi)) { + return null + } + // dtmi:com:example:Thermostat;1 -> dtmi/com/example/thermostat-1.json + return `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` +} + +/** + * @description Returns external IDs in `extend` and `component` elements * @param {{ extends: any[]; contents: any[]; }} rootJson * @returns {Array} */ -const getDependencies = dtdlJson => { - const deps = [] - if (dtdlJson.extends) { - if (Array.isArray(dtdlJson.extends)) { - dtdlJson.extends.forEach(e => deps.push(e)) +const getDependencies = rootJson => { + let deps = [] + if (Array.isArray(rootJson)) { + deps = rootJson.map(d => d['@id']) + return deps + } + if (rootJson.extends) { + if (Array.isArray(rootJson.extends)) { + rootJson.extends.forEach(e => deps.push(e)) } else { - deps.push(dtdlJson.extends) + deps.push(rootJson.extends) } } - if (dtdlJson.contents) { - const comps = dtdlJson.contents.filter(c => c['@type'] === 'Component') + if (rootJson.contents) { + const comps = rootJson.contents.filter(c => c['@type'] === 'Component') comps.forEach(c => { if (typeof c.schema !== 'object') { if (deps.indexOf(c.schema) === -1) { @@ -31,7 +52,35 @@ const getDependencies = dtdlJson => { return deps } -export/** +/** + * @description Checks all dependencies are available + * @param {Array} deps + * @returns {boolean} + */ +const checkDependencies = dtmi => { + let result = true + const fileName = path.join(__dirname, dtmiToPath(dtmi)) + console.log(`Validating dependencies for ${dtmi} from ${fileName}`) + const dtdlJson = JSON.parse(fs.readFileSync(fileName, 'utf-8')) + const deps = getDependencies(dtdlJson) + deps.forEach(d => { + const fileName = path.join(__dirname, dtmiToPath(d)) + if (fs.existsSync(fileName)) { + console.log(`Dependency ${d} found`) + const model = JSON.parse(fs.readFileSync(fileName, 'utf-8')) + if (model['@id'] !== d) { + console.error(`ERROR: LowerCase issue with dependent id ${d}. Was ${model['@id']}. Aborting`) + result = result && true + } + } else { + console.error(`ERROR: Dependency ${d} NOT found. Aborting`) + result = false + } + }) + return result +} + +/** * @description Validates all internal IDs follow the namepspace set by the root id * @param {any} dtdlJson * @returns {boolean} @@ -41,7 +90,8 @@ const checkIds = dtdlJson => { console.log(`checkIds: validating root ${rootId}`) const ids = jsonata('**."@id"').evaluate(dtdlJson) if (Array.isArray(ids)) { - for (const id in ids) { + for (let i = 0; i < ids.length; i++) { + const id = ids[i] console.log('found: ' + id) if (!id.split(';')[0].startsWith(rootId.split(';')[0])) { console.log(`ERROR: Document id ${id} does not satisfy the root id ${rootId}`) @@ -56,7 +106,7 @@ const checkIds = dtdlJson => { } } -export /** +/** * @description Checks if the folder/name convention matches the DTMI * @param {string} file * @returns {boolean} @@ -65,9 +115,9 @@ const checkDtmiPathFromFile = file => { const model = JSON.parse(fs.readFileSync(file, 'utf-8')) const id = model['@id'] if (id) { - const expectedPath = path.normalize(dtmi2path(model['@id'])) - if (file !== expectedPath) { - console.log(`ERROR: in current path ${file}, expecting ${expectedPath}.`) + const expectedPath = path.normalize(dtmiToPath(model['@id'])) + if (path.normalize('/' + file) !== expectedPath) { + console.log(`ERROR: in current path ${path.normalize(file)}, expecting ${expectedPath}.`) return false } else { console.log(`FilePath ${file} for ${id} seems OK.`) @@ -78,3 +128,4 @@ const checkDtmiPathFromFile = file => { return false } } +module.exports = { dtmiToPath, isDtmi, checkIds, getDependencies, checkDependencies, checkDtmiPathFromFile } diff --git a/repo-convention.test.js b/repo-convention.test.js deleted file mode 100644 index 47a164f..0000000 --- a/repo-convention.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import fs from 'fs' -import { dtmi2path, getDependencies, checkIds, checkDtmiPathFromFile } from './repo-convention.js' - -const readFile = file => { - return JSON.parse(fs.readFileSync(file, 'utf-8')) -} - -test('invalid dtmi', () => { - expect(dtmi2path('')).toBe('NOT-VALID-DTMI') - expect(dtmi2path('notadtmi')).toBe('NOT-VALID-DTMI') - expect(dtmi2path('dtmi:notadtmi')).toBe('NOT-VALID-DTMI') - expect(dtmi2path('dtmi:com:example:thermostat:1')).toBe('NOT-VALID-DTMI') - expect(dtmi2path('dtmi:com:example-bad:thermostat;1')).toBe('NOT-VALID-DTMI') -}) - -test('dtmi to path', () => { - expect(dtmi2path('dtmi:com:example:Thermostat;1')).toBe('dtmi/com/example/thermostat-1.json') - expect(dtmi2path('dtmi:com:Example:thermostat;1')).toBe('dtmi/com/example/thermostat-1.json') -}) - -test('get dependencies', () => { - expect(getDependencies(readFile('dtmi/test/uniqueids-1.json'))).toEqual([]) - expect(getDependencies(readFile('dtmi/test/onedep-1.json'))).toEqual(['dtmi:test:base;1']) - expect(getDependencies(readFile('dtmi/test/twodeps-1.json'))).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) -}) - -test('check ids', () => { - expect(checkIds(readFile('dtmi/test/uniqueids-1.json'))).toBe(false) - expect(checkIds(readFile('dtmi/test/onedep-1.json'))).toBe(true) -}) - -test('checkDtmiPathFromFile', () => { - expect(checkDtmiPathFromFile('dtmi/test/uniqueids-1.json')).toBe(true) - expect(checkDtmiPathFromFile('dtmi/test/badpath.json')).toBe(false) -}) From 7dd3f55a9b56553091b0ebf3327dc815eb39e282 Mon Sep 17 00:00:00 2001 From: rido-min Date: Sun, 11 Oct 2020 21:31:32 -0700 Subject: [PATCH 03/14] refactors --- .npmignore | 3 +-- package.json | 2 +- repo-convention.js | 8 +------- test/repo-convention.test.js | 1 + test/test-models.js | 19 +++++++++++++++++-- add-model.js => tools/add-model.js | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) rename add-model.js => tools/add-model.js (97%) diff --git a/.npmignore b/.npmignore index 85aabbd..11dd952 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,2 @@ .github/ -dtmi/ -*.test.js \ No newline at end of file +dtmi/ \ No newline at end of file diff --git a/package.json b/package.json index 48b1e3e..608344d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@ridomin/repo-scripts", "version": "0.0.5", "description": "", - "main": "dtmi2path.js", + "main": "repo-convention.js", "type": "module", "scripts": { "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", diff --git a/repo-convention.js b/repo-convention.js index 917e440..52e4c50 100644 --- a/repo-convention.js +++ b/repo-convention.js @@ -13,13 +13,7 @@ const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A * @param {string} dtmi * @returns {string} */ -const dtmiToPath = dtmi => { - if (!isDtmi(dtmi)) { - return null - } - // dtmi:com:example:Thermostat;1 -> dtmi/com/example/thermostat-1.json - return `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` -} +const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null /** * @description Returns external IDs in `extend` and `component` elements diff --git a/test/repo-convention.test.js b/test/repo-convention.test.js index 1e9dec5..b244f06 100644 --- a/test/repo-convention.test.js +++ b/test/repo-convention.test.js @@ -18,6 +18,7 @@ test('get dependencies', () => { expect(rc.getDependencies(td.noDepsJson)).toEqual([]) expect(rc.getDependencies(td.oneDepJson)).toEqual(['dtmi:test:base;1']) expect(rc.getDependencies(td.twoDepsJson)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) + expect(rc.getDependencies(td.twoDepsJsonExtendsArray)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) }) test('check ids', () => { diff --git a/test/test-models.js b/test/test-models.js index 4b870bc..08fc660 100644 --- a/test/test-models.js +++ b/test/test-models.js @@ -31,7 +31,7 @@ const globalId = { ] } -const twoDepsJson = { +const twoDepsJsonExtendsArray = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -46,4 +46,19 @@ const twoDepsJson = { ] } -module.exports = { noDepsJson, oneDepJson, twoDepsJson, globalId } +const twoDepsJson = { + '@context': 'dtmi:dtdl:context;2', + '@id': 'dtmi:test:twodeps;1', + '@type': 'Interface', + displayName: 'onedep', + extends: 'dtmi:test:base;1', + contents: [ + { + '@type': 'Component', + name: 'temperature', + schema: 'dtmi:test:onedep:comp1;1' + } + ] +} + +module.exports = { noDepsJson, oneDepJson, twoDepsJson, twoDepsJsonExtendsArray, globalId } diff --git a/add-model.js b/tools/add-model.js similarity index 97% rename from add-model.js rename to tools/add-model.js index ab39664..cef7bc5 100644 --- a/add-model.js +++ b/tools/add-model.js @@ -25,7 +25,7 @@ const createInterfaceFromJson = jsonDtdl => { * @description Adds a model to the repo. Validates ids, dependencies and set the right folder/file name * @param {string} file */ -const addModel = (file) => { +const addModel = file => { const rootJson = JSON.parse(fs.readFileSync(file, 'utf-8')) if (Array.isArray(rootJson)) { From 9b7588697ba44f61b36a075dd6c865ec40c204b5 Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:12:22 -0700 Subject: [PATCH 04/14] use imports with ts --- .github/workflows/validate-deps.js | 6 +-- .github/workflows/validate-files.js | 4 +- .github/workflows/validate-files.yml | 5 ++ .github/workflows/validate-ids.js | 5 +- .gitignore | 3 +- .npmignore | 3 +- main.ts | 4 ++ package.json | 4 ++ repo-convention.d.ts | 4 ++ repo-convention.js | 20 ++++---- test/repo-convention.test.js | 8 ++-- test/test-models.js | 12 ++--- tsconfig.json | 69 ++++++++++++++++++++++++++++ 13 files changed, 117 insertions(+), 30 deletions(-) create mode 100644 main.ts create mode 100644 repo-convention.d.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/validate-deps.js b/.github/workflows/validate-deps.js index 29d5865..87bec18 100644 --- a/.github/workflows/validate-deps.js +++ b/.github/workflows/validate-deps.js @@ -1,6 +1,6 @@ -const path = require('path') -const fs = require('fs') -const { checkDependencies } = require('../../repo-convention') +import fs from 'fs' +import path from 'path' +import { checkDependencies } from '../../repo-convention' for (let i = 1; i < process.argv.length; i++) { const file = path.normalize((path.join(process.cwd(), process.argv[i]))) diff --git a/.github/workflows/validate-files.js b/.github/workflows/validate-files.js index 1344271..8932c79 100644 --- a/.github/workflows/validate-files.js +++ b/.github/workflows/validate-files.js @@ -1,5 +1,5 @@ -const path = require('path') -const { checkDtmiPathFromFile } = require('../../repo-convention') +import path from 'path' +import { checkDtmiPathFromFile } from '../../repo-convention' for (let i = 1; i < process.argv.length; i++) { const file = path.normalize(process.argv[i]) diff --git a/.github/workflows/validate-files.yml b/.github/workflows/validate-files.yml index 2a2c54f..5378264 100644 --- a/.github/workflows/validate-files.yml +++ b/.github/workflows/validate-files.yml @@ -16,6 +16,11 @@ jobs: - id: files uses: jitterbit/get-changed-files@v1 + - name: Use Node.js 14.x + uses: actions/setup-node@v1 + with: + node-version: 14.x + - name: npm install run: npm install diff --git a/.github/workflows/validate-ids.js b/.github/workflows/validate-ids.js index 8367307..b020dd9 100644 --- a/.github/workflows/validate-ids.js +++ b/.github/workflows/validate-ids.js @@ -1,5 +1,6 @@ -const path = require('path') -const fs = require('fs') +import fs from 'fs' +import path from 'path' + const { checkIds } = require('../../repo-convention') for (let i = 2; i < process.argv.length; i++) { diff --git a/.gitignore b/.gitignore index 48e4eaa..f078218 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -.npmrc \ No newline at end of file +.npmrc +out/ \ No newline at end of file diff --git a/.npmignore b/.npmignore index 11dd952..1f9fea0 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,3 @@ .github/ -dtmi/ \ No newline at end of file +dtmi/ +out/ \ No newline at end of file diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..c27d4cb --- /dev/null +++ b/main.ts @@ -0,0 +1,4 @@ +import { isDtmi, dtmiToPath } from './repo-convention.js' +const dtmi = 'dtmi:com:example:Thermostat;1' +console.log(isDtmi(dtmi)) +console.log(dtmiToPath(dtmi)) diff --git a/package.json b/package.json index 608344d..d5cce21 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "standard" : { "globals" : [ "test", "expect" + ], + "ignore" : [ + "out/*", + "*.d.ts" ] } } diff --git a/repo-convention.d.ts b/repo-convention.d.ts new file mode 100644 index 0000000..a590c95 --- /dev/null +++ b/repo-convention.d.ts @@ -0,0 +1,4 @@ +export declare module 'repo-convention' { + function isDtmi(dtmi: string) : boolean; + function dtmiToPath(dtmi: string) : string; +} \ No newline at end of file diff --git a/repo-convention.js b/repo-convention.js index 52e4c50..3789e0c 100644 --- a/repo-convention.js +++ b/repo-convention.js @@ -1,26 +1,26 @@ -const fs = require('fs') -const path = require('path') -const jsonata = require('jsonata') +import fs from 'fs' +import path from 'path' +import jsonata from 'jsonata' /** * @description Validates DTMI with RegEx from https://github.com/Azure/digital-twin-model-identifier#validation-regular-expressions * @param {string} dtmi */ -const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) +export const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) /** * @description Converts DTMI to /dtmi/com/example/device-1.json path. * @param {string} dtmi * @returns {string} */ -const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null +export const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null /** * @description Returns external IDs in `extend` and `component` elements * @param {{ extends: any[]; contents: any[]; }} rootJson * @returns {Array} */ -const getDependencies = rootJson => { +export const getDependencies = rootJson => { let deps = [] if (Array.isArray(rootJson)) { deps = rootJson.map(d => d['@id']) @@ -51,7 +51,7 @@ const getDependencies = rootJson => { * @param {Array} deps * @returns {boolean} */ -const checkDependencies = dtmi => { +export const checkDependencies = dtmi => { let result = true const fileName = path.join(__dirname, dtmiToPath(dtmi)) console.log(`Validating dependencies for ${dtmi} from ${fileName}`) @@ -79,7 +79,7 @@ const checkDependencies = dtmi => { * @param {any} dtdlJson * @returns {boolean} */ -const checkIds = dtdlJson => { +export const checkIds = dtdlJson => { const rootId = dtdlJson['@id'] console.log(`checkIds: validating root ${rootId}`) const ids = jsonata('**."@id"').evaluate(dtdlJson) @@ -105,7 +105,7 @@ const checkIds = dtdlJson => { * @param {string} file * @returns {boolean} */ -const checkDtmiPathFromFile = file => { +export const checkDtmiPathFromFile = file => { const model = JSON.parse(fs.readFileSync(file, 'utf-8')) const id = model['@id'] if (id) { @@ -122,4 +122,4 @@ const checkDtmiPathFromFile = file => { return false } } -module.exports = { dtmiToPath, isDtmi, checkIds, getDependencies, checkDependencies, checkDtmiPathFromFile } +// module.exports = { dtmiToPath, isDtmi, checkIds, getDependencies, checkDependencies, checkDtmiPathFromFile } diff --git a/test/repo-convention.test.js b/test/repo-convention.test.js index b244f06..dbbb7d3 100644 --- a/test/repo-convention.test.js +++ b/test/repo-convention.test.js @@ -1,5 +1,5 @@ -const rc = require('../repo-convention.js') -const td = require('./test-models') +import * as rc from '../repo-convention.js' +import * as td from './test-models' test('invalid dtmi', () => { expect(rc.dtmiToPath('')).toBe(null) @@ -27,6 +27,6 @@ test('check ids', () => { }) test('checkDtmiPathFromFile', () => { - expect(rc.checkDtmiPathFromFile('../dtmi/test/onedep-1.json')).toBe(true) - expect(rc.checkDtmiPathFromFile('badpath.json')).toBe(false) + expect(rc.checkDtmiPathFromFile('dtmi/test/onedep-1.json')).toBe(true) + expect(rc.checkDtmiPathFromFile('test/badpath.json')).toBe(false) }) diff --git a/test/test-models.js b/test/test-models.js index 08fc660..3735b47 100644 --- a/test/test-models.js +++ b/test/test-models.js @@ -1,4 +1,4 @@ -const noDepsJson = { +export const noDepsJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:onedep;1', '@type': 'Interface', @@ -6,7 +6,7 @@ const noDepsJson = { contents: [] } -const oneDepJson = { +export const oneDepJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:onedep;1', '@type': 'Interface', @@ -15,7 +15,7 @@ const oneDepJson = { contents: [] } -const globalId = { +export const globalId = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -31,7 +31,7 @@ const globalId = { ] } -const twoDepsJsonExtendsArray = { +export const twoDepsJsonExtendsArray = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -46,7 +46,7 @@ const twoDepsJsonExtendsArray = { ] } -const twoDepsJson = { +export const twoDepsJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -60,5 +60,3 @@ const twoDepsJson = { } ] } - -module.exports = { noDepsJson, oneDepJson, twoDepsJson, twoDepsJsonExtendsArray, globalId } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bb6d472 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,69 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./out", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} From 78b67ba5591a506aeefea20c44fe83f921b7f5c8 Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:14:47 -0700 Subject: [PATCH 05/14] run files on push --- .github/workflows/validate-files.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/validate-files.yml b/.github/workflows/validate-files.yml index 5378264..33c481b 100644 --- a/.github/workflows/validate-files.yml +++ b/.github/workflows/validate-files.yml @@ -1,6 +1,9 @@ name: validate-files on: + push: + branches: [rido/*] + pull_request: branches: [main] paths: From 3e286994e18ef7d08f6215e15b3b031165d17d48 Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:17:05 -0700 Subject: [PATCH 06/14] import with .js --- .github/workflows/validate-deps.js | 2 +- .github/workflows/validate-files.js | 2 +- .github/workflows/validate-ids.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate-deps.js b/.github/workflows/validate-deps.js index 87bec18..8c04e7a 100644 --- a/.github/workflows/validate-deps.js +++ b/.github/workflows/validate-deps.js @@ -1,6 +1,6 @@ import fs from 'fs' import path from 'path' -import { checkDependencies } from '../../repo-convention' +import { checkDependencies } from '../../repo-convention.js' for (let i = 1; i < process.argv.length; i++) { const file = path.normalize((path.join(process.cwd(), process.argv[i]))) diff --git a/.github/workflows/validate-files.js b/.github/workflows/validate-files.js index 8932c79..af126a3 100644 --- a/.github/workflows/validate-files.js +++ b/.github/workflows/validate-files.js @@ -1,5 +1,5 @@ import path from 'path' -import { checkDtmiPathFromFile } from '../../repo-convention' +import { checkDtmiPathFromFile } from '../../repo-convention.js' for (let i = 1; i < process.argv.length; i++) { const file = path.normalize(process.argv[i]) diff --git a/.github/workflows/validate-ids.js b/.github/workflows/validate-ids.js index b020dd9..6464df1 100644 --- a/.github/workflows/validate-ids.js +++ b/.github/workflows/validate-ids.js @@ -1,7 +1,7 @@ import fs from 'fs' import path from 'path' -const { checkIds } = require('../../repo-convention') +const { checkIds } = require('../../repo-convention.js') for (let i = 2; i < process.argv.length; i++) { const file = path.normalize((path.join(process.cwd(), process.argv[i]))) From fab3dfe1f140cec5caa988d9e406e6b476903804 Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:18:30 -0700 Subject: [PATCH 07/14] fix import in ids --- .github/workflows/validate-ids.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-ids.js b/.github/workflows/validate-ids.js index 6464df1..fd26417 100644 --- a/.github/workflows/validate-ids.js +++ b/.github/workflows/validate-ids.js @@ -1,7 +1,7 @@ import fs from 'fs' import path from 'path' -const { checkIds } = require('../../repo-convention.js') +import { checkIds } from '../../repo-convention.js' for (let i = 2; i < process.argv.length; i++) { const file = path.normalize((path.join(process.cwd(), process.argv[i]))) From f604c42a551c5d51099d0b4373690cd85b064daa Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:19:51 -0700 Subject: [PATCH 08/14] edit files --- dtmi/test/badpath.json | 2 +- dtmi/test/onedep-1.json | 2 +- dtmi/test/twodeps-1.json | 2 +- dtmi/test/uniqueids-1.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dtmi/test/badpath.json b/dtmi/test/badpath.json index d5b26bb..96eafae 100644 --- a/dtmi/test/badpath.json +++ b/dtmi/test/badpath.json @@ -2,7 +2,7 @@ "@context": "dtmi:dtdl:context;2", "@id": "dtmi:test:onedep;1", "@type": "Interface", - "displayName": "onedep", + "displayName": "onedep1", "extends": ["dtmi:test:base;1"], "contents": [ { diff --git a/dtmi/test/onedep-1.json b/dtmi/test/onedep-1.json index c468f15..2a37362 100644 --- a/dtmi/test/onedep-1.json +++ b/dtmi/test/onedep-1.json @@ -2,7 +2,7 @@ "@context": "dtmi:dtdl:context;2", "@id": "dtmi:test:onedep;1", "@type": "Interface", - "displayName": "onedep", + "displayName": "onedep1", "extends": "dtmi:test:base;1", "contents": [] } diff --git a/dtmi/test/twodeps-1.json b/dtmi/test/twodeps-1.json index c2484fa..638fdbd 100644 --- a/dtmi/test/twodeps-1.json +++ b/dtmi/test/twodeps-1.json @@ -2,7 +2,7 @@ "@context": "dtmi:dtdl:context;2", "@id": "dtmi:test:twodeps;1", "@type": "Interface", - "displayName": "onedep", + "displayName": "onedep1", "extends": ["dtmi:test:base;1"], "contents": [ { diff --git a/dtmi/test/uniqueids-1.json b/dtmi/test/uniqueids-1.json index 667dbfd..39ebdba 100644 --- a/dtmi/test/uniqueids-1.json +++ b/dtmi/test/uniqueids-1.json @@ -2,7 +2,7 @@ "@context": "dtmi:dtdl:context;2", "@id": "dtmi:test:uniqueids;1", "@type": "Interface", - "displayName": "onedep", + "displayName": "onedep1", "contents": [ { "@id": "dtmi:test:prop;1", From 43b483ab08421c7d3d5f43299ce63510e248a44f Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:30:35 -0700 Subject: [PATCH 09/14] 0.7 --- package.json | 2 +- repo-convention.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d5cce21..adb5ad2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ridomin/repo-scripts", - "version": "0.0.5", + "version": "0.0.7", "description": "", "main": "repo-convention.js", "type": "module", diff --git a/repo-convention.d.ts b/repo-convention.d.ts index a590c95..62da304 100644 --- a/repo-convention.d.ts +++ b/repo-convention.d.ts @@ -1,4 +1,4 @@ -export declare module 'repo-convention' { +export declare module '@ridomin/repo-scripts' { function isDtmi(dtmi: string) : boolean; function dtmiToPath(dtmi: string) : string; } \ No newline at end of file From 5946073ac1cf6f8d3657c61491c429ab21192721 Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:40:36 -0700 Subject: [PATCH 10/14] prepare 0.8 --- .npmignore | 3 +- README.md | 4 +-- main.ts | 4 --- package.json | 2 +- repo-convention.d.ts | 3 ++ tools/add-model.js | 8 ++--- tsconfig.json | 69 -------------------------------------------- 7 files changed, 12 insertions(+), 81 deletions(-) delete mode 100644 main.ts delete mode 100644 tsconfig.json diff --git a/.npmignore b/.npmignore index 1f9fea0..690b1fd 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,4 @@ .github/ dtmi/ -out/ \ No newline at end of file +out/ +test/ diff --git a/README.md b/README.md index 9189231..9688dd4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ npm i @ridomin/repo-scripts ``` ```js -import { dtmi2path } from '@ridomin/repo-scripts/dtmi2path.js' +import { dtmi2path } from '@ridomin/repo-scripts' const dtmi = 'dtmi:com:example:Thermostat;1' console.log(dtmi, '->', dtmi2path(dtmi)) -``` \ No newline at end of file +``` diff --git a/main.ts b/main.ts deleted file mode 100644 index c27d4cb..0000000 --- a/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { isDtmi, dtmiToPath } from './repo-convention.js' -const dtmi = 'dtmi:com:example:Thermostat;1' -console.log(isDtmi(dtmi)) -console.log(dtmiToPath(dtmi)) diff --git a/package.json b/package.json index adb5ad2..5e738ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ridomin/repo-scripts", - "version": "0.0.7", + "version": "0.0.8", "description": "", "main": "repo-convention.js", "type": "module", diff --git a/repo-convention.d.ts b/repo-convention.d.ts index 62da304..22a8af7 100644 --- a/repo-convention.d.ts +++ b/repo-convention.d.ts @@ -1,4 +1,7 @@ export declare module '@ridomin/repo-scripts' { function isDtmi(dtmi: string) : boolean; function dtmiToPath(dtmi: string) : string; + function getDependencies(dtdlJson: any): Array; + function checkDependencies(dtmi: string): boolean; + function checkIds(dtdlJson: any): boolean; } \ No newline at end of file diff --git a/tools/add-model.js b/tools/add-model.js index cef7bc5..68a5e42 100644 --- a/tools/add-model.js +++ b/tools/add-model.js @@ -1,7 +1,7 @@ -const fs = require('fs') -const path = require('path') -const mkdirp = require('mkdirp') -const { dtmiToPath, checkIds } = require('../repo-convention.js') +import fs from 'fs' +import path from 'path' +import mkdirp from 'mkdirp' +import { dtmiToPath, checkIds } from '../repo-convention.js' const createInterfaceFromFile = file => { const jsonDtdl = JSON.parse(fs.readFileSync(file, 'utf-8')) diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index bb6d472..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./out", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -} From dbaa4e64c2cdff6befaf153ece36c88ad6a8fe82 Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:51:35 -0700 Subject: [PATCH 11/14] 0.9 upd readme --- README.md | 21 +++++++++++++++++---- package.json | 2 +- repo-convention.d.ts | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9688dd4..8de6f54 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,21 @@ To use with `Node.js 12.x` add the `--experimental-modules` flag. (Tested with ` npm i @ridomin/repo-scripts ``` -```js -import { dtmi2path } from '@ridomin/repo-scripts' -const dtmi = 'dtmi:com:example:Thermostat;1' -console.log(dtmi, '->', dtmi2path(dtmi)) +```ts +// main.ts +import * as rc from '@ridomin/repo-scripts' +console.log(rc.isDtmi('aaa')) +console.log(rc.dtmiToPath('dtmi:com:ee:aa;1')) +``` + +Build with + +```bash +tsc -t es6 --outDir ./out --moduleResolution node .\main.ts +``` + +Run with + +```bash +node ./out/main.js ``` diff --git a/package.json b/package.json index 5e738ac..c4933a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ridomin/repo-scripts", - "version": "0.0.8", + "version": "0.0.9", "description": "", "main": "repo-convention.js", "type": "module", diff --git a/repo-convention.d.ts b/repo-convention.d.ts index 22a8af7..86bea66 100644 --- a/repo-convention.d.ts +++ b/repo-convention.d.ts @@ -1,4 +1,4 @@ -export declare module '@ridomin/repo-scripts' { +declare module '@ridomin/repo-scripts' { function isDtmi(dtmi: string) : boolean; function dtmiToPath(dtmi: string) : string; function getDependencies(dtdlJson: any): Array; From 59d11e5c9d52e5e3e99e0e19b027e50df4a2c21e Mon Sep 17 00:00:00 2001 From: rido-min Date: Mon, 12 Oct 2020 00:54:14 -0700 Subject: [PATCH 12/14] move comments, add mkdir dep --- package-lock.json | 34 +++++++++++++++++++++++++--------- package.json | 12 +++++++----- repo-convention.d.ts | 28 ++++++++++++++++++++++++++++ repo-convention.js | 24 ------------------------ 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index a90ab44..ed7d715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "@iotmodels/repo-scripts", - "version": "1.0.0", + "name": "@ridomin/repo-scripts", + "version": "0.0.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1728,6 +1728,15 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -3991,13 +4000,9 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "ms": { "version": "2.1.2", @@ -6007,6 +6012,17 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } } }, "write-file-atomic": { diff --git a/package.json b/package.json index c4933a5..ff1b0e6 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,15 @@ "standard": "^14.3.4" }, "dependencies": { - "jsonata": "^1.8.3" + "jsonata": "^1.8.3", + "mkdirp": "^1.0.4" }, - "standard" : { - "globals" : [ - "test", "expect" + "standard": { + "globals": [ + "test", + "expect" ], - "ignore" : [ + "ignore": [ "out/*", "*.d.ts" ] diff --git a/repo-convention.d.ts b/repo-convention.d.ts index 86bea66..9d77775 100644 --- a/repo-convention.d.ts +++ b/repo-convention.d.ts @@ -1,7 +1,35 @@ declare module '@ridomin/repo-scripts' { + /** + * @description Validates DTMI with RegEx from https://github.com/Azure/digital-twin-model-identifier#validation-regular-expressions + * @param {string} dtmi + */ function isDtmi(dtmi: string) : boolean; + + /** + * @description Converts DTMI to /dtmi/com/example/device-1.json path. + * @param {string} dtmi + * @returns {string} + */ function dtmiToPath(dtmi: string) : string; + + /** + * @description Returns external IDs in `extend` and `component` elements + * @param {{ extends: any[]; contents: any[]; }} rootJson + * @returns {Array} + */ function getDependencies(dtdlJson: any): Array; + + /** + * @description Checks all dependencies are available + * @param {Array} deps + * @returns {boolean} + */ function checkDependencies(dtmi: string): boolean; + + /** + * @description Validates all internal IDs follow the namepspace set by the root id + * @param {any} dtdlJson + * @returns {boolean} + */ function checkIds(dtdlJson: any): boolean; } \ No newline at end of file diff --git a/repo-convention.js b/repo-convention.js index 3789e0c..830e938 100644 --- a/repo-convention.js +++ b/repo-convention.js @@ -2,24 +2,10 @@ import fs from 'fs' import path from 'path' import jsonata from 'jsonata' -/** - * @description Validates DTMI with RegEx from https://github.com/Azure/digital-twin-model-identifier#validation-regular-expressions - * @param {string} dtmi - */ export const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) -/** - * @description Converts DTMI to /dtmi/com/example/device-1.json path. - * @param {string} dtmi - * @returns {string} - */ export const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null -/** - * @description Returns external IDs in `extend` and `component` elements - * @param {{ extends: any[]; contents: any[]; }} rootJson - * @returns {Array} - */ export const getDependencies = rootJson => { let deps = [] if (Array.isArray(rootJson)) { @@ -46,11 +32,6 @@ export const getDependencies = rootJson => { return deps } -/** - * @description Checks all dependencies are available - * @param {Array} deps - * @returns {boolean} - */ export const checkDependencies = dtmi => { let result = true const fileName = path.join(__dirname, dtmiToPath(dtmi)) @@ -74,11 +55,6 @@ export const checkDependencies = dtmi => { return result } -/** - * @description Validates all internal IDs follow the namepspace set by the root id - * @param {any} dtdlJson - * @returns {boolean} - */ export const checkIds = dtdlJson => { const rootId = dtdlJson['@id'] console.log(`checkIds: validating root ${rootId}`) From 4b9925cbbcfaf277932bd4b2ac3aa3d8e4fb92d4 Mon Sep 17 00:00:00 2001 From: rido-min Date: Sun, 1 Nov 2020 22:53:56 -0800 Subject: [PATCH 13/14] upd scripts --- .github/ISSUE_TEMPLATE/code_owner_request.md | 22 ------- .../ISSUE_TEMPLATE/model_edit_or_removal.md | 16 ----- .../pull_request_template.md | 19 ------ .../workflows/push_expanded_to_storage.yml | 47 -------------- .github/workflows/push_to_storage.yml | 30 --------- .github/workflows/validate-models.yml | 44 ------------- .../devicemanagement/deviceinformation-1.json | 64 +++++++++++++++++++ package.json | 4 +- repo-convention.js | 58 ++++++++++++----- test/repo-convention.test.js | 11 ++-- test/test-models.js | 23 ++----- tools/add-model.js | 46 +++++++------ 12 files changed, 146 insertions(+), 238 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/code_owner_request.md delete mode 100644 .github/ISSUE_TEMPLATE/model_edit_or_removal.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md delete mode 100644 .github/workflows/push_expanded_to_storage.yml delete mode 100644 .github/workflows/push_to_storage.yml delete mode 100644 .github/workflows/validate-models.yml create mode 100644 dtmi/azure/devicemanagement/deviceinformation-1.json diff --git a/.github/ISSUE_TEMPLATE/code_owner_request.md b/.github/ISSUE_TEMPLATE/code_owner_request.md deleted file mode 100644 index 3374785..0000000 --- a/.github/ISSUE_TEMPLATE/code_owner_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Code owner request -about: Use this template to request code owner over a particular path -title: Code owner request -labels: code owner -assignees: '' - ---- - -GitHub username(s): - -Repository path(s)/DTMI(s): - -Have you submitted a device model to this path/DTMI (yes/no): - -Username(s) for any existing code owners for this path: - -How many DTDL documents currently exist in this path: - -Justification: - -Is this a request to remove an existing code owner? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/model_edit_or_removal.md b/.github/ISSUE_TEMPLATE/model_edit_or_removal.md deleted file mode 100644 index 0b4557d..0000000 --- a/.github/ISSUE_TEMPLATE/model_edit_or_removal.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Model edit or removal request -about: Use this template to request removal of models from this repository -title: Model edit or removal -labels: model removal, model edit -assignees: '' - ---- - -Repository path and DTMI (one issue per file/DTMI): - -Are you a listed CODEOWNER? - -Username(s) for any other code owners for this path: - -Justification: diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md deleted file mode 100644 index 6e516b0..0000000 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ /dev/null @@ -1,19 +0,0 @@ - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -Thank you for contributing to the Device Models repository! - -This checklist is used to make sure that common guidelines for a pull request are followed. - -## General Guidelines - -- [ ] The pull request **only** contains device model documents? -- [ ] All device models are in JSON-LD format? -- [ ] One device model per file? -- [ ] A `@context` property at the root of the DTDL document specifying the DTDL version ("dtmi:dtdl:context;2")? -- [ ] One `@id` property specified at the root of the DTDL document? -- [ ] The root `@id` is a valid DTMI? -- [ ] The file path follows the naming conventions in `./CONTRIBUTING.md`? -- [ ] The file path is lower case? -- [ ] Any sub-Id is namespaced "under" the root DTMI? -- [ ] Any references to DTMIs can be resolved within **this** repository (either as existing documents or included in this pull request)? diff --git a/.github/workflows/push_expanded_to_storage.yml b/.github/workflows/push_expanded_to_storage.yml deleted file mode 100644 index 1e1dff8..0000000 --- a/.github/workflows/push_expanded_to_storage.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: push_expanded_to_azure_storage - -on: - push: - branches: [main] - paths: - - 'dtmi/**/*.json' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - id: files - uses: jitterbit/get-changed-files@v1 - - - name: Configure GitHub feed - run: dotnet nuget add source https://nuget.pkg.github.com/Azure/index.json -n github -u Azure -p ${{secrets.GITHUB_TOKEN}} --store-password-in-clear-text - - - name: Install resolver client - run: dotnet tool install -g dmr-client --add-source https://nuget.pkg.github.com/Azure/index.json - - - name: Expand models - run: | - for changed_file in ${{ steps.files.outputs.added_modified }}; do - if [[ ${changed_file} =~ ^dtmi([\/\\][a-z0-9_]+){2,}-([1-9][0-9]{0,8}).json$ ]]; then - dmr-client resolve --model-file ${changed_file} --repository $PWD -o ${changed_file//.json/.expanded.json} --silent - fi - done - shell: bash - - - name: CLI push files - uses: azure/CLI@v1 - with: - inlineScript: | - for changed_file in ${{ steps.files.outputs.added_modified }}; do - if [[ ${changed_file} =~ ^dtmi([\/\\][a-z0-9_]+){2,}-([1-9][0-9]{0,8}).json$ ]]; then - expanded_file=${changed_file//.json/.expanded.json} - - echo "Submitting ${expanded_file} to Azure Storage" - az storage blob upload --connection-string "${{ secrets.STORAGE_CONNECTION_STRING }}" --container-name '$web' --name "${expanded_file}" --file "${expanded_file}" --no-progress - else - echo "Skipping ${changed_file}. It doesn't appear to be a DTDL document." - fi - done diff --git a/.github/workflows/push_to_storage.yml b/.github/workflows/push_to_storage.yml deleted file mode 100644 index 6bf2cad..0000000 --- a/.github/workflows/push_to_storage.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: push_to_azure_storage - -on: - push: - branches: [main] - paths: - - 'dtmi/**/*.json' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - id: files - uses: jitterbit/get-changed-files@v1 - - - name: CLI push files - uses: azure/CLI@v1 - with: - inlineScript: | - for changed_file in ${{ steps.files.outputs.added_modified }}; do - if [[ ${changed_file} =~ ^dtmi([\/\\][a-z0-9_]+){2,}-([1-9][0-9]{0,8}).json$ ]]; then - echo "Submitting ${changed_file} to Azure Storage" - az storage blob upload --connection-string "${{ secrets.STORAGE_CONNECTION_STRING }}" --container-name '$web' --name "${changed_file}" --file "${changed_file}" --no-progress - else - echo "Skipping ${changed_file}. It doesn't appear to be a DTDL document." - fi - done diff --git a/.github/workflows/validate-models.yml b/.github/workflows/validate-models.yml deleted file mode 100644 index 1a9e658..0000000 --- a/.github/workflows/validate-models.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: validate-models - -on: - pull_request: - branches: [main] - paths: - - 'dtmi/**' - -jobs: - validate-models: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - id: files - uses: jitterbit/get-changed-files@v1 - - - name: Configure GitHub feed - run: dotnet nuget add source https://nuget.pkg.github.com/Azure/index.json -n github -u Azure -p ${{secrets.GITHUB_TOKEN}} --store-password-in-clear-text - - - name: Install resolver client - run: dotnet tool install -g dmr-client --add-source https://nuget.pkg.github.com/Azure/index.json - - - name: Parse Models - run: | - for f in ${{ steps.files.outputs.added_modified }}; - do - if [[ $f == *"dtmi/"* ]] - then - echo "validating $f" - dmr-client validate --model-file $f --repository $PWD - if [ $? -eq 0 ] - then - echo "validation ok: $f" - else - echo "error validating model" - exit 1 - fi - else - echo "skipping $f" - fi - done - shell: bash diff --git a/dtmi/azure/devicemanagement/deviceinformation-1.json b/dtmi/azure/devicemanagement/deviceinformation-1.json new file mode 100644 index 0000000..8a37e6d --- /dev/null +++ b/dtmi/azure/devicemanagement/deviceinformation-1.json @@ -0,0 +1,64 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "@type": "Interface", + "displayName": "Device Information", + "contents": [ + { + "@type": "Property", + "name": "manufacturer", + "displayName": "Manufacturer", + "schema": "string", + "description": "Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso." + }, + { + "@type": "Property", + "name": "model", + "displayName": "Device model", + "schema": "string", + "description": "Device model name or ID. Ex. Surface Book 2." + }, + { + "@type": "Property", + "name": "swVersion", + "displayName": "Software version", + "schema": "string", + "description": "Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45" + }, + { + "@type": "Property", + "name": "osName", + "displayName": "Operating system name", + "schema": "string", + "description": "Name of the operating system on the device. Ex. Windows 10 IoT Core." + }, + { + "@type": "Property", + "name": "processorArchitecture", + "displayName": "Processor architecture", + "schema": "string", + "description": "Architecture of the processor on the device. Ex. x64 or ARM." + }, + { + "@type": "Property", + "name": "processorManufacturer", + "displayName": "Processor manufacturer", + "schema": "string", + "description": "Name of the manufacturer of the processor on the device. Ex. Intel." + }, + { + "@type": "Property", + "name": "totalStorage", + "displayName": "Total storage", + "schema": "double", + "description": "Total available storage on the device in kilobytes. Ex. 2048000 kilobytes." + }, + { + "@type": "Property", + "name": "totalMemory", + "displayName": "Total memory", + "schema": "double", + "description": "Total available memory on the device in kilobytes. Ex. 256000 kilobytes." + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index ff1b0e6..a5cdf27 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "@ridomin/repo-scripts", - "version": "0.0.9", + "version": "0.0.10", "description": "", "main": "repo-convention.js", - "type": "module", + "type": "commonjs", "scripts": { "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "prepare": "standard && npm test" diff --git a/repo-convention.js b/repo-convention.js index 830e938..a25bcfa 100644 --- a/repo-convention.js +++ b/repo-convention.js @@ -1,12 +1,26 @@ -import fs from 'fs' -import path from 'path' -import jsonata from 'jsonata' +const fs = require('fs') +const path = require('path') +const jsonata = require('jsonata') -export const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) +/** + * @description Validates DTMI with RegEx from https://github.com/Azure/digital-twin-model-identifier#validation-regular-expressions + * @param {string} dtmi + */ +const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) -export const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null +/** + * @description Converts DTMI to /dtmi/com/example/device-1.json path. + * @param {string} dtmi + * @returns {string} + */ +const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null -export const getDependencies = rootJson => { +/** + * @description Returns external IDs in `extend` and `component` elements + * @param {{ extends: any[]; contents: any[]; }} rootJson + * @returns {Array} + */ +const getDependencies = rootJson => { let deps = [] if (Array.isArray(rootJson)) { deps = rootJson.map(d => d['@id']) @@ -32,7 +46,12 @@ export const getDependencies = rootJson => { return deps } -export const checkDependencies = dtmi => { +/** + * @description Checks all dependencies are available + * @param {Array} deps + * @returns {boolean} + */ +const checkDependencies = dtmi => { let result = true const fileName = path.join(__dirname, dtmiToPath(dtmi)) console.log(`Validating dependencies for ${dtmi} from ${fileName}`) @@ -55,7 +74,12 @@ export const checkDependencies = dtmi => { return result } -export const checkIds = dtdlJson => { +/** + * @description Validates all internal IDs follow the namepspace set by the root id + * @param {any} dtdlJson + * @returns {boolean} + */ +const checkIds = dtdlJson => { const rootId = dtdlJson['@id'] console.log(`checkIds: validating root ${rootId}`) const ids = jsonata('**."@id"').evaluate(dtdlJson) @@ -63,15 +87,19 @@ export const checkIds = dtdlJson => { for (let i = 0; i < ids.length; i++) { const id = ids[i] console.log('found: ' + id) + if (!isDtmi(id)) { + console.log(`ERROR: Document id ${id} is not a valid DTMI.`) + return false + } if (!id.split(';')[0].startsWith(rootId.split(';')[0])) { - console.log(`ERROR: Document id ${id} does not satisfy the root id ${rootId}`) + console.log(`ERROR: Document id ${id} does not satisfy the root id ${rootId}.`) return false } } - console.log(`checkIds: validated ${ids.length} ids`) + console.log(`checkIds: Validated: ${ids.length} ids are under the root DTMI.`) return true } else { - console.log('checkIds: ids not found') + console.log('checkIds: Validated: Global ids not found.') return true } } @@ -81,12 +109,12 @@ export const checkIds = dtdlJson => { * @param {string} file * @returns {boolean} */ -export const checkDtmiPathFromFile = file => { +const checkDtmiPathFromFile = file => { const model = JSON.parse(fs.readFileSync(file, 'utf-8')) const id = model['@id'] if (id) { - const expectedPath = path.normalize(dtmiToPath(model['@id'])) - if (path.normalize('/' + file) !== expectedPath) { + const expectedPath = path.join(process.cwd(), dtmiToPath(model['@id'])) + if (path.resolve(file) !== expectedPath) { console.log(`ERROR: in current path ${path.normalize(file)}, expecting ${expectedPath}.`) return false } else { @@ -98,4 +126,4 @@ export const checkDtmiPathFromFile = file => { return false } } -// module.exports = { dtmiToPath, isDtmi, checkIds, getDependencies, checkDependencies, checkDtmiPathFromFile } +module.exports = { dtmiToPath, isDtmi, checkIds, getDependencies, checkDependencies, checkDtmiPathFromFile } diff --git a/test/repo-convention.test.js b/test/repo-convention.test.js index dbbb7d3..abddda7 100644 --- a/test/repo-convention.test.js +++ b/test/repo-convention.test.js @@ -1,5 +1,9 @@ -import * as rc from '../repo-convention.js' -import * as td from './test-models' +const rc = require('../repo-convention.js') +const td = require('./test-models') + +test('is valid dtmi', () => { + expect(rc.isDtmi('dtmi:with::twosemicolons;1')).toBe(false) +}) test('invalid dtmi', () => { expect(rc.dtmiToPath('')).toBe(null) @@ -18,7 +22,6 @@ test('get dependencies', () => { expect(rc.getDependencies(td.noDepsJson)).toEqual([]) expect(rc.getDependencies(td.oneDepJson)).toEqual(['dtmi:test:base;1']) expect(rc.getDependencies(td.twoDepsJson)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) - expect(rc.getDependencies(td.twoDepsJsonExtendsArray)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) }) test('check ids', () => { @@ -27,6 +30,6 @@ test('check ids', () => { }) test('checkDtmiPathFromFile', () => { - expect(rc.checkDtmiPathFromFile('dtmi/test/onedep-1.json')).toBe(true) + expect(rc.checkDtmiPathFromFile('dtmi/azure/devicemanagement/deviceinformation-1.json')).toBe(true) expect(rc.checkDtmiPathFromFile('test/badpath.json')).toBe(false) }) diff --git a/test/test-models.js b/test/test-models.js index 3735b47..4b870bc 100644 --- a/test/test-models.js +++ b/test/test-models.js @@ -1,4 +1,4 @@ -export const noDepsJson = { +const noDepsJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:onedep;1', '@type': 'Interface', @@ -6,7 +6,7 @@ export const noDepsJson = { contents: [] } -export const oneDepJson = { +const oneDepJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:onedep;1', '@type': 'Interface', @@ -15,7 +15,7 @@ export const oneDepJson = { contents: [] } -export const globalId = { +const globalId = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -31,7 +31,7 @@ export const globalId = { ] } -export const twoDepsJsonExtendsArray = { +const twoDepsJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -46,17 +46,4 @@ export const twoDepsJsonExtendsArray = { ] } -export const twoDepsJson = { - '@context': 'dtmi:dtdl:context;2', - '@id': 'dtmi:test:twodeps;1', - '@type': 'Interface', - displayName: 'onedep', - extends: 'dtmi:test:base;1', - contents: [ - { - '@type': 'Component', - name: 'temperature', - schema: 'dtmi:test:onedep:comp1;1' - } - ] -} +module.exports = { noDepsJson, oneDepJson, twoDepsJson, globalId } diff --git a/tools/add-model.js b/tools/add-model.js index 68a5e42..3c5a646 100644 --- a/tools/add-model.js +++ b/tools/add-model.js @@ -1,51 +1,55 @@ -import fs from 'fs' -import path from 'path' -import mkdirp from 'mkdirp' -import { dtmiToPath, checkIds } from '../repo-convention.js' +const fs = require('fs') +const path = require('path') +const mkdirp = require('mkdirp') +const { dtmiToPath, checkIds, checkDependencies } = require('../repo-convention.js') -const createInterfaceFromFile = file => { +const createInterfaceFromFile = async file => { const jsonDtdl = JSON.parse(fs.readFileSync(file, 'utf-8')) - createInterfaceFromJson(jsonDtdl) + await createInterfaceFromJson(jsonDtdl) } -const createInterfaceFromJson = jsonDtdl => { +const createInterfaceFromJson = async jsonDtdl => { const dtmi = jsonDtdl['@id'] const fileName = path.join(process.cwd(), dtmiToPath(dtmi)) if (fs.existsSync(fileName)) { - console.log(`ERROR: ID ${dtmi} already exists at ${fileName} . Aborting `) - return false - } - mkdirp(path.dirname(fileName)).then(m => { + console.log(`WARNING: ID ${dtmi} already exists at ${fileName} . Skipping `) + } else { + await mkdirp(path.dirname(fileName)) fs.writeFileSync(fileName, JSON.stringify(jsonDtdl, null, 2)) console.log(`Model ${dtmi} added successfully to ${fileName}`) - }) + } } /** * @description Adds a model to the repo. Validates ids, dependencies and set the right folder/file name * @param {string} file */ -const addModel = file => { +const addModel = async file => { const rootJson = JSON.parse(fs.readFileSync(file, 'utf-8')) - if (Array.isArray(rootJson)) { - rootJson.forEach(d => { + for await (const d of rootJson) { checkIds(d) - createInterfaceFromJson(d) - }) + await createInterfaceFromJson(d) + } return rootJson[0]['@id'] } else { checkIds(rootJson) - createInterfaceFromFile(file) + await createInterfaceFromFile(file) return rootJson['@id'] } } -const main = () => { +const main = async () => { const file = process.argv[2] console.log(`processing: ${file}`) - const id = addModel(file) - console.log(id) + const id = await addModel(file) + console.log('added', id) + if (id && !checkDependencies(id)) { + fs.unlinkSync(path.join(process.cwd(), dtmiToPath(id))) + console.log('ERROR: Dont forget to include all the dependencies before submitting.') + process.exit(1) + } + console.log(`SUCCESS: File ${file} added to ${dtmiToPath(id)}`) } main() From cb6d2c492673b693f2d530e3ae152571055f36d9 Mon Sep 17 00:00:00 2001 From: "Ricardo Minguez (Rido)" Date: Mon, 2 Nov 2020 14:30:02 -0800 Subject: [PATCH 14/14] Use ES6 modules in package (#1) * add es6 * rev 0.12 with ES6 support * em addModel * resolver zero * 0.14 async with deps * stuck with BOM --- repo-convention.d.ts => index.d.ts | 7 +++ index.js | 1 + package-lock.json | 17 ++++++- package.json | 11 +++-- repo-convention.js | 76 ++++++++++++++++++++++-------- test/repo-convention.test.js | 52 +++++++++++++------- test/test-models.js | 10 ++-- tools/add-model.js | 24 +++++----- tsconfig.json | 69 +++++++++++++++++++++++++++ 9 files changed, 208 insertions(+), 59 deletions(-) rename repo-convention.d.ts => index.d.ts (83%) create mode 100644 index.js create mode 100644 tsconfig.json diff --git a/repo-convention.d.ts b/index.d.ts similarity index 83% rename from repo-convention.d.ts rename to index.d.ts index 9d77775..409e0e4 100644 --- a/repo-convention.d.ts +++ b/index.d.ts @@ -32,4 +32,11 @@ declare module '@ridomin/repo-scripts' { * @returns {boolean} */ function checkIds(dtdlJson: any): boolean; + + /** + * @description Copy DTDL file to the /dtmi/com/model-1.json folder struct + * @param file + * @returns The root DTMI of the file, if successfull + */ + function addModel(file: string): string; } \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..42d0615 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +export { isDtmi, dtmiToPath, getDependencies, checkIds, checkDtmiPathFromFile } from './repo-convention.js' diff --git a/package-lock.json b/package-lock.json index ed7d715..31f36c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@ridomin/repo-scripts", - "version": "0.0.9", + "version": "0.0.13", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -712,6 +712,16 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "26.0.15", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz", + "integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==", + "dev": true, + "requires": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, "@types/node": { "version": "14.11.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", @@ -4047,6 +4057,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index a5cdf27..8560809 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "@ridomin/repo-scripts", - "version": "0.0.10", + "version": "0.0.14", "description": "", - "main": "repo-convention.js", - "type": "commonjs", + "main": "index.js", + "type": "module", + "types": "index.d.ts", "scripts": { "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "prepare": "standard && npm test" @@ -20,12 +21,14 @@ }, "homepage": "https://github.com/iotmodels/repo-scripts/#readme", "devDependencies": { + "@types/jest": "^26.0.15", "jest": "^26.4.2", "standard": "^14.3.4" }, "dependencies": { "jsonata": "^1.8.3", - "mkdirp": "^1.0.4" + "mkdirp": "^1.0.4", + "node-fetch": "^2.6.1" }, "standard": { "globals": [ diff --git a/repo-convention.js b/repo-convention.js index a25bcfa..f058844 100644 --- a/repo-convention.js +++ b/repo-convention.js @@ -1,21 +1,22 @@ -const fs = require('fs') -const path = require('path') -const jsonata = require('jsonata') +import { readFileSync, existsSync } from 'fs' +import { join, resolve, normalize } from 'path' +import jsonata from 'jsonata' +import fetch from 'node-fetch' -/** +export/** * @description Validates DTMI with RegEx from https://github.com/Azure/digital-twin-model-identifier#validation-regular-expressions * @param {string} dtmi */ const isDtmi = dtmi => RegExp('^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$').test(dtmi) -/** +export/** * @description Converts DTMI to /dtmi/com/example/device-1.json path. * @param {string} dtmi * @returns {string} */ const dtmiToPath = dtmi => isDtmi(dtmi) ? `/${dtmi.toLowerCase().replace(/:/g, '/').replace(';', '-')}.json` : null -/** +export/** * @description Returns external IDs in `extend` and `component` elements * @param {{ extends: any[]; contents: any[]; }} rootJson * @returns {Array} @@ -46,22 +47,22 @@ const getDependencies = rootJson => { return deps } -/** +export/** * @description Checks all dependencies are available * @param {Array} deps * @returns {boolean} */ const checkDependencies = dtmi => { let result = true - const fileName = path.join(__dirname, dtmiToPath(dtmi)) + const fileName = join(__dirname, dtmiToPath(dtmi) || '') console.log(`Validating dependencies for ${dtmi} from ${fileName}`) - const dtdlJson = JSON.parse(fs.readFileSync(fileName, 'utf-8')) + const dtdlJson = JSON.parse(readFileSync(fileName, 'utf-8')) const deps = getDependencies(dtdlJson) deps.forEach(d => { - const fileName = path.join(__dirname, dtmiToPath(d)) - if (fs.existsSync(fileName)) { + const fileName = join(__dirname, dtmiToPath(d) || '') + if (existsSync(fileName)) { console.log(`Dependency ${d} found`) - const model = JSON.parse(fs.readFileSync(fileName, 'utf-8')) + const model = JSON.parse(readFileSync(fileName, 'utf-8')) if (model['@id'] !== d) { console.error(`ERROR: LowerCase issue with dependent id ${d}. Was ${model['@id']}. Aborting`) result = result && true @@ -74,7 +75,7 @@ const checkDependencies = dtmi => { return result } -/** +export/** * @description Validates all internal IDs follow the namepspace set by the root id * @param {any} dtdlJson * @returns {boolean} @@ -104,18 +105,18 @@ const checkIds = dtdlJson => { } } -/** +export/** * @description Checks if the folder/name convention matches the DTMI * @param {string} file * @returns {boolean} */ const checkDtmiPathFromFile = file => { - const model = JSON.parse(fs.readFileSync(file, 'utf-8')) + const model = JSON.parse(readFileSync(file, 'utf-8')) const id = model['@id'] if (id) { - const expectedPath = path.join(process.cwd(), dtmiToPath(model['@id'])) - if (path.resolve(file) !== expectedPath) { - console.log(`ERROR: in current path ${path.normalize(file)}, expecting ${expectedPath}.`) + const expectedPath = join(process.cwd(), dtmiToPath(model['@id']) || '') + if (resolve(file) !== expectedPath) { + console.log(`ERROR: in current path ${normalize(file)}, expecting ${expectedPath}.`) return false } else { console.log(`FilePath ${file} for ${id} seems OK.`) @@ -126,4 +127,41 @@ const checkDtmiPathFromFile = file => { return false } } -module.exports = { dtmiToPath, isDtmi, checkIds, getDependencies, checkDependencies, checkDtmiPathFromFile } + +export /** +* @param {string} dtmi +* @param {string | undefined} [repo] +* @param {undefined} [expanded] +* @returns {Array} +*/ +const resolveDtmi = async (dtmi, repo, expanded) => { + const result = [] + if (!repo) repo = 'https://' + 'devicemodels.azure.com' + const url = `${repo}${dtmiToPath(dtmi)}` + + if (expanded) { + const xurl = url.replace('.json', '.expanded.json') + const expRaw = await (await fetch(xurl)).text() + const exp = JSON.parse(expRaw) + exp.foreach(doc => { + const id = doc['@id'] + result.push({ id, doc }) + }) + return result + } + + const respJson = await (await fetch(url)).json() + const id = respJson['@id'] + if (id === dtmi) { + result.push({ id, respJson }) + const deps = getDependencies(respJson) + for await (const d of deps) { + const json = await (await fetch(`${repo}${dtmiToPath(d)}`)).json() + result.push({ d, json }) + } + } else { + console.error('ERR. Case diff ', id) + } + // console.log(result) + return result +} diff --git a/test/repo-convention.test.js b/test/repo-convention.test.js index abddda7..be11ad6 100644 --- a/test/repo-convention.test.js +++ b/test/repo-convention.test.js @@ -1,35 +1,53 @@ -const rc = require('../repo-convention.js') -const td = require('./test-models') +import { isDtmi, dtmiToPath, getDependencies, checkIds, checkDtmiPathFromFile, resolveDtmi } from '../repo-convention.js' +import { noDepsJson, oneDepJson, twoDepsJson, globalId } from './test-models' test('is valid dtmi', () => { - expect(rc.isDtmi('dtmi:with::twosemicolons;1')).toBe(false) + expect(isDtmi('dtmi:with::twosemicolons;1')).toBe(false) }) test('invalid dtmi', () => { - expect(rc.dtmiToPath('')).toBe(null) - expect(rc.dtmiToPath('notadtmi')).toBe(null) - expect(rc.dtmiToPath('dtmi:notadtmi')).toBe(null) - expect(rc.dtmiToPath('dtmi:com:example:thermostat:1')).toBe(null) - expect(rc.dtmiToPath('dtmi:com:example-bad:thermostat;1')).toBe(null) + expect(dtmiToPath('')).toBe(null) + expect(dtmiToPath('notadtmi')).toBe(null) + expect(dtmiToPath('dtmi:notadtmi')).toBe(null) + expect(dtmiToPath('dtmi:com:example:thermostat:1')).toBe(null) + expect(dtmiToPath('dtmi:com:example-bad:thermostat;1')).toBe(null) }) test('dtmi to path', () => { - expect(rc.dtmiToPath('dtmi:com:example:Thermostat;1')).toBe('/dtmi/com/example/thermostat-1.json') - expect(rc.dtmiToPath('dtmi:com:Example:thermostat;1')).toBe('/dtmi/com/example/thermostat-1.json') + expect(dtmiToPath('dtmi:com:example:Thermostat;1')).toBe('/dtmi/com/example/thermostat-1.json') + expect(dtmiToPath('dtmi:com:Example:thermostat;1')).toBe('/dtmi/com/example/thermostat-1.json') }) test('get dependencies', () => { - expect(rc.getDependencies(td.noDepsJson)).toEqual([]) - expect(rc.getDependencies(td.oneDepJson)).toEqual(['dtmi:test:base;1']) - expect(rc.getDependencies(td.twoDepsJson)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) + expect(getDependencies(noDepsJson)).toEqual([]) + expect(getDependencies(oneDepJson)).toEqual(['dtmi:test:base;1']) + expect(getDependencies(twoDepsJson)).toEqual(['dtmi:test:base;1', 'dtmi:test:onedep:comp1;1']) }) test('check ids', () => { - expect(rc.checkIds(td.globalId)).toBe(false) - expect(rc.checkIds(td.oneDepJson)).toBe(true) + expect(checkIds(globalId)).toBe(false) + expect(checkIds(oneDepJson)).toBe(true) }) test('checkDtmiPathFromFile', () => { - expect(rc.checkDtmiPathFromFile('dtmi/azure/devicemanagement/deviceinformation-1.json')).toBe(true) - expect(rc.checkDtmiPathFromFile('test/badpath.json')).toBe(false) + expect(checkDtmiPathFromFile('dtmi/azure/devicemanagement/deviceinformation-1.json')).toBe(true) + expect(checkDtmiPathFromFile('test/badpath.json')).toBe(false) +}) + +test('resolveDtmi_noDeps', async () => { + const models = await resolveDtmi('dtmi:azure:DeviceManagement:DeviceInformation;1') + console.log(models.length) + expect(models.length).toBe(1) +}) + +test('resolveDtmi_Deps', async () => { + const models = await resolveDtmi('dtmi:com:example:TemperatureController;1') + console.log(models.length) + expect(models.length).toBe(3) +}) + +test('resolveDtmi_DepsExpanded', async () => { + const models = await resolveDtmi('dtmi:Espressif:SensorController;2', null, true) + console.log(models.length) + expect(models.length).toBe(6) }) diff --git a/test/test-models.js b/test/test-models.js index 4b870bc..8d82e20 100644 --- a/test/test-models.js +++ b/test/test-models.js @@ -1,4 +1,4 @@ -const noDepsJson = { +export const noDepsJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:onedep;1', '@type': 'Interface', @@ -6,7 +6,7 @@ const noDepsJson = { contents: [] } -const oneDepJson = { +export const oneDepJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:onedep;1', '@type': 'Interface', @@ -15,7 +15,7 @@ const oneDepJson = { contents: [] } -const globalId = { +export const globalId = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -31,7 +31,7 @@ const globalId = { ] } -const twoDepsJson = { +export const twoDepsJson = { '@context': 'dtmi:dtdl:context;2', '@id': 'dtmi:test:twodeps;1', '@type': 'Interface', @@ -45,5 +45,3 @@ const twoDepsJson = { } ] } - -module.exports = { noDepsJson, oneDepJson, twoDepsJson, globalId } diff --git a/tools/add-model.js b/tools/add-model.js index 3c5a646..4cc07c4 100644 --- a/tools/add-model.js +++ b/tools/add-model.js @@ -1,31 +1,31 @@ -const fs = require('fs') -const path = require('path') -const mkdirp = require('mkdirp') -const { dtmiToPath, checkIds, checkDependencies } = require('../repo-convention.js') +import { readFileSync, existsSync, writeFileSync, unlinkSync } from 'fs' +import { join, dirname } from 'path' +import mkdirp from 'mkdirp' +import { dtmiToPath, checkIds, checkDependencies } from '../repo-convention.js' const createInterfaceFromFile = async file => { - const jsonDtdl = JSON.parse(fs.readFileSync(file, 'utf-8')) + const jsonDtdl = JSON.parse(readFileSync(file, 'utf-8')) await createInterfaceFromJson(jsonDtdl) } const createInterfaceFromJson = async jsonDtdl => { const dtmi = jsonDtdl['@id'] - const fileName = path.join(process.cwd(), dtmiToPath(dtmi)) - if (fs.existsSync(fileName)) { + const fileName = join(process.cwd(), dtmiToPath(dtmi) || '') + if (existsSync(fileName)) { console.log(`WARNING: ID ${dtmi} already exists at ${fileName} . Skipping `) } else { - await mkdirp(path.dirname(fileName)) - fs.writeFileSync(fileName, JSON.stringify(jsonDtdl, null, 2)) + await mkdirp(dirname(fileName)) + writeFileSync(fileName, JSON.stringify(jsonDtdl, null, 2)) console.log(`Model ${dtmi} added successfully to ${fileName}`) } } -/** +export/** * @description Adds a model to the repo. Validates ids, dependencies and set the right folder/file name * @param {string} file */ const addModel = async file => { - const rootJson = JSON.parse(fs.readFileSync(file, 'utf-8')) + const rootJson = JSON.parse(readFileSync(file, 'utf-8')) if (Array.isArray(rootJson)) { for await (const d of rootJson) { checkIds(d) @@ -45,7 +45,7 @@ const main = async () => { const id = await addModel(file) console.log('added', id) if (id && !checkDependencies(id)) { - fs.unlinkSync(path.join(process.cwd(), dtmiToPath(id))) + unlinkSync(join(process.cwd(), dtmiToPath(id) || '')) console.log('ERROR: Dont forget to include all the dependencies before submitting.') process.exit(1) } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0c3af5f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,69 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + "allowJs": true, /* Allow javascript files to be compiled. */ + "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +}