From 13aa7f3f794b5d84eabc53a093b4258153194a88 Mon Sep 17 00:00:00 2001 From: Mike Gray Date: Sun, 25 Jun 2023 22:58:24 -0500 Subject: [PATCH] fix: idempotency for retrofit, improved docs Also some projen housekeeping --- .github/workflows/upgrade-main.yml | 3 +- .projenrc.ts | 6 ++ API.md | 66 ++++++++++++------ README.md | 4 +- src/files/{README.md.ts => README.ts} | 0 src/index.ts | 67 ++++++++++++------- .../OVOSSkillProject.test.ts.snap | 10 +-- 7 files changed, 101 insertions(+), 55 deletions(-) rename src/files/{README.md.ts => README.ts} (100%) diff --git a/.github/workflows/upgrade-main.yml b/.github/workflows/upgrade-main.yml index 0442b57..14e93b1 100644 --- a/.github/workflows/upgrade-main.yml +++ b/.github/workflows/upgrade-main.yml @@ -38,7 +38,8 @@ jobs: needs: upgrade runs-on: ubuntu-latest permissions: - contents: read + contents: write + pull-requests: write if: ${{ needs.upgrade.outputs.patch_created }} steps: - name: Checkout diff --git a/.projenrc.ts b/.projenrc.ts index 85de826..76e0fc3 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -29,4 +29,10 @@ project.compileTask.exec( 'cp src/files/* lib/files', ); +const upgradeMain = project.tryFindObjectFile('.github/workflows/upgrade-main.yml'); +upgradeMain?.addOverride('jobs.pr.steps.4.with.token', '${{ secrets.GITHUB_TOKEN }}'); +upgradeMain?.addOverride('jobs.pr.permissions.pull-requests', 'write'); +upgradeMain?.addOverride('jobs.pr.permissions.contents', 'write'); + + project.synth(); diff --git a/API.md b/API.md index 59e726f..f1540cb 100644 --- a/API.md +++ b/API.md @@ -73,10 +73,12 @@ It will not: - Overwrite your README.md file, if it exists, or create one if it does not exist - Create sample code - Touch your LICENSE file - - Note that official OVOS and Neon skills have skill license requirements that may not be compatible with your existing license, if you want to submit it as part of one of those organizations. Please review the [OVOS skill license requirements](https://openvoiceos.github.io/ovos-technical-manual/license/). + - Note that official OVOS and Neon skills have skill license requirements that may not be compatible with your existing license, if you want to submit it as part of one of those organizations. Please review the [OVOS skill license requirements](https://openvoiceos.github.io/ovos-technical-manual/license/). If you are not submitting your skill to OVOS or Neon, you can use any license you like, and should set `skillLicenseTest: false` in your `.projenrc.json` file. Once the retrofit is complete, you can review the changes needed for modernization with `grep TODO __init__.py`. This project attempts to handle as many as possible, but due to differences in code style and structure, some changes will need to be made manually. +If your skill code is not in `__init__.py` in the repository root, the retrofit code won't be able to find it. PRs welcome to add support for other skill structures. + # API Reference @@ -125,7 +127,6 @@ const oVOSSkillProjectOptions: OVOSSkillProjectOptions = { ... } | stale | boolean | Auto-close of stale issues and pull request. | | staleOptions | projen.github.StaleOptions | Auto-close stale issues and pull requests. | | vscode | boolean | Enable VSCode integration. | -| skillClass | string | The name of the skill class. | | author | string | The name of the skill's author. | | authorAddress | string | The email address of the skill's author. | | authorHandle | string | The GitHub handle of the skill's author. | @@ -137,8 +138,10 @@ const oVOSSkillProjectOptions: OVOSSkillProjectOptions = { ... } | repositoryUrl | string | The URL of the skill's GitHub repository. | | retrofit | boolean | Retrofit an existing Mycroft skill to OVOS? | | sampleCode | boolean | Include sample code? | +| skillClass | string | The name of the skill class. | | skillDescription | string | The description of the skill. | | skillKeywords | string | Keywords for your skill package. | +| skillLicenseTest | boolean | Include a test to check that the skill's license is FOSS? | --- @@ -556,25 +559,6 @@ Enabled by default for root projects. Disabled for non-root projects. --- -##### `skillClass`Required - -```typescript -public readonly skillClass: string; -``` - -- *Type:* string - -The name of the skill class. - ---- - -*Example* - -```typescript -HelloWorldSkill -``` - - ##### `author`Optional ```typescript @@ -766,6 +750,25 @@ Include sample code? --- +##### `skillClass`Optional + +```typescript +public readonly skillClass: string; +``` + +- *Type:* string + +The name of the skill class. + +--- + +*Example* + +```typescript +HelloWorldSkill +``` + + ##### `skillDescription`Optional ```typescript @@ -801,6 +804,19 @@ Keywords for your skill package. --- +##### `skillLicenseTest`Optional + +```typescript +public readonly skillLicenseTest: boolean; +``` + +- *Type:* boolean +- *Default:* true + +Include a test to check that the skill's license is FOSS? + +--- + ## Classes ### OVOSSkillProject @@ -1150,11 +1166,17 @@ The name of the directory to create sample code in. ##### `createGithubWorkflows` ```typescript -public createGithubWorkflows(): void +public createGithubWorkflows(skillLicenseTest: boolean): void ``` Create OVOS standard Github Actions workflows. +###### `skillLicenseTest`Required + +- *Type:* boolean + +--- + ##### `restructureLocaleFolders` ```typescript diff --git a/README.md b/README.md index f83007d..e79c67d 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ It will not: - Overwrite your README.md file, if it exists, or create one if it does not exist - Create sample code - Touch your LICENSE file - - Note that official OVOS and Neon skills have skill license requirements that may not be compatible with your existing license, if you want to submit it as part of one of those organizations. Please review the [OVOS skill license requirements](https://openvoiceos.github.io/ovos-technical-manual/license/). + - Note that official OVOS and Neon skills have skill license requirements that may not be compatible with your existing license, if you want to submit it as part of one of those organizations. Please review the [OVOS skill license requirements](https://openvoiceos.github.io/ovos-technical-manual/license/). If you are not submitting your skill to OVOS or Neon, you can use any license you like, and should set `skillLicenseTest: false` in your `.projenrc.json` file. Once the retrofit is complete, you can review the changes needed for modernization with `grep TODO __init__.py`. This project attempts to handle as many as possible, but due to differences in code style and structure, some changes will need to be made manually. + +If your skill code is not in `__init__.py` in the repository root, the retrofit code won't be able to find it. PRs welcome to add support for other skill structures. diff --git a/src/files/README.md.ts b/src/files/README.ts similarity index 100% rename from src/files/README.md.ts rename to src/files/README.ts diff --git a/src/index.ts b/src/index.ts index 76d1b72..edf0f5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFile import { join } from 'path'; import { ProjenrcJson, SampleDir, SampleFile, TextFile } from 'projen'; import { GitHubProject, GitHubProjectOptions } from 'projen/lib/github'; -import { readmeMd } from './files/README.md'; +import { readmeMd } from './files/README'; import { setupPy } from './files/setup.py'; import { LicenseTestsWorkflow, ProposeReleaseWorkflow, PublishAlphaWorkflow, PublishReleaseWorkflow, SkillTestsWorkflow, UpdateSkillJsonWorkflow } from './GithubWorkflows'; @@ -17,7 +17,7 @@ export interface OVOSSkillProjectOptions extends GitHubProjectOptions { * The name of the skill class * @example HelloWorldSkill */ - readonly skillClass: string; + readonly skillClass?: string; /** * The name of the skill's PyPi package * @example ovos-hello-world-skill @@ -86,6 +86,11 @@ export interface OVOSSkillProjectOptions extends GitHubProjectOptions { * @example "A simple skill that says hello world" */ readonly skillDescription?: string; + /** + * Include a test to check that the skill's license is FOSS? + * @default true + */ + readonly skillLicenseTest?: boolean; } export class OVOSSkillProject extends GitHubProject { @@ -95,11 +100,15 @@ export class OVOSSkillProject extends GitHubProject { */ static modernizeSkillCode(file: string) { let existingSkillFileContents = readFileSync(file).toString(); - const ovosImports = `from ovos_workshop.decorators import intent_handler + let ovosImports = `# TODO: Remove unused OVOS imports +from ovos_workshop.decorators import intent_handler from ovos_workshop.skills import OVOSSkill from ovos_utils.intents import IntentBuilder from ovos_bus_client.message import Message`; // Replacements + if (existingSkillFileContents.includes('AdaptIntent')) { + ovosImports += '\nfrom ovos_utils.intents import AdaptIntent'; + } let skillFileArray = existingSkillFileContents.split('\n'); skillFileArray.forEach((line, index) => { // Comment out Mycroft imports @@ -130,28 +139,30 @@ ${line}`; constructor(options: OVOSSkillProjectOptions) { // Default options - const author = options.author ?? 'TODO: Your Name'; + const author = options.author ?? 'TODO: Add \'author\' to .projenrc.json and run pj'; + const repositoryUrl = options.repositoryUrl ?? 'TODO: Add \'repositoryUrl\' to .projenrc.json and run pj'; + const authorAddress = options.authorAddress ?? 'TODO: Add \'authorAddress\' to .projenrc.json and run pj'; + const license = options.license ?? '# TODO: Add \'license\' to .projenrc.json and run pj'; + const skillClass = options.skillClass ?? 'TODO: Add \'skillClass\' to .projenrc.json and run pj'; const retrofit = options.retrofit ?? false; - const repositoryUrl = options.repositoryUrl ?? 'TODO: PLACEHOLDER'; - const packageDir = options.packageDir ?? 'src'; const pypiName = options.pypiName ?? process.cwd().split('/').pop()!; - const authorAddress = options.authorAddress ?? 'TODO: Your Email'; - const authorHandle = options.authorHandle ?? ''; const sampleCode = options.sampleCode ?? true; const skillKeywords = options.skillKeywords ?? 'ovos skill'; const condenseLocaleFolders = options.condenseLocaleFolders ?? true; const githubworkflows = options.githubWorkflows ?? true; - const license = options.license ?? 'Apache-2.0'; const skillDescription = options.skillDescription ?? ''; + const skillLicenseTest = options.skillLicenseTest ?? true; + let packageDir = options.packageDir ?? 'src'; + if (retrofit && packageDir === 'src') { packageDir = '';} // Super let superProps = { ...options }; if (!retrofit) { superProps.readme = { contents: readmeMd({ - skillClass: options.skillClass, - authorName: author, - authorHandle: authorHandle, + skillClass: options.skillClass ?? 'OVOSSkill', + authorName: options.author ?? 'authorName', + authorHandle: options.authorHandle ?? 'githubUsername', skillKeywords: skillKeywords, }), }; @@ -180,7 +191,7 @@ ${line}`; authorAddress: authorAddress, license: license, description: skillDescription, - skillClass: options.skillClass, + skillClass: skillClass, }).split('\n'), }); new SampleFile(this, 'skill.json', { @@ -196,16 +207,16 @@ ${line}`; lines: requirements.split('\n'), }); } - if (existsSync('__init__.py')) { + if (existsSync('__init__.py') && !existsSync('setup.py')) { OVOSSkillProject.modernizeSkillCode('__init__.py'); - } else { - const todoMd = readFileSync('files/TODO.md').toString(); + } else if (!existsSync('__init__.py') && !existsSync('setup.py')) { + const todoMd = readFileSync(join(__dirname, 'files', 'TODO.md')).toString(); writeFileSync('TODO.md', `Could not find __init__.py, please update your skill manually:\n${todoMd}`); } }; // Github Actions if (githubworkflows) { - this.createGithubWorkflows(); + this.createGithubWorkflows(skillLicenseTest); } this.createDevBranch(); } @@ -367,8 +378,10 @@ ${line}`; /** * Create OVOS standard Github Actions workflows. */ - createGithubWorkflows() { - new LicenseTestsWorkflow(this.github!); + createGithubWorkflows(skillLicenseTest: boolean) { + if (skillLicenseTest) { + new LicenseTestsWorkflow(this.github!); + } new ProposeReleaseWorkflow(this.github!); new PublishAlphaWorkflow(this.github!); new PublishReleaseWorkflow(this.github!); @@ -392,26 +405,28 @@ ${line}`; */ restructureLocaleFolders(sourceFolder: string) { ['vocab', 'dialog', 'regex', 'intents'].forEach((dir) => { + const locale = join(sourceFolder, 'locale'); try { - const languageDirs = readdirSync(`${sourceFolder}/${dir}`, { withFileTypes: true }) + mkdirSync(locale, { recursive: true }); + const languageDirs = readdirSync(join(sourceFolder, dir), { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); languageDirs.forEach((lang) => { - if (!existsSync(`${sourceFolder}/locale/${lang}`)) { - mkdirSync(`${sourceFolder}/locale/${lang}`, { recursive: true }); + if (!existsSync(join(locale, lang))) { + mkdirSync(join(locale, lang), { recursive: true }); } // Check if a directory already exists at the new path - if (existsSync(`${sourceFolder}/locale/${lang}/${dir}`)) { - console.warn(`Directory already exists: ${sourceFolder}/locale/${lang}/${dir}. Skipping...`); + if (existsSync(join(locale, lang, dir))) { + console.warn(`Directory already exists: ${join(locale, lang, dir)}. Skipping...`); } else { - renameSync(`${sourceFolder}/${dir}/${lang}`, `${sourceFolder}/locale/${lang}/${dir}`); + renameSync(join(sourceFolder, dir, lang), join(locale, lang, dir)); } }); } catch (err) { - console.error(err); + console.debug(err); } }); } diff --git a/test/__snapshots__/OVOSSkillProject.test.ts.snap b/test/__snapshots__/OVOSSkillProject.test.ts.snap index 580b7e0..ec6f05a 100644 --- a/test/__snapshots__/OVOSSkillProject.test.ts.snap +++ b/test/__snapshots__/OVOSSkillProject.test.ts.snap @@ -433,7 +433,7 @@ Information about your skill ## Credits -TODO: Your Name (@) +authorName (@githubUsername) ## Category @@ -448,7 +448,7 @@ from setuptools import setup from os import walk, path BASEDIR = path.abspath(path.dirname(__file__)) -URL = \\"TODO: PLACEHOLDER\\" +URL = \\"TODO: Add 'repositoryUrl' to .projenrc.json and run pj\\" SKILL_CLAZZ = \\"TestSkill\\" # needs to match __init__.py class name PYPI_NAME = \\"test-skill\\" # pip install PYPI_NAME @@ -513,9 +513,9 @@ setup( long_description=long_description, long_description_content_type=\\"text/markdown\\", url=URL, - author=\\"TODO: Your Name\\", - author_email=\\"TODO: Your Email\\", - license=\\"Apache-2.0\\", + author=\\"TODO: Add 'author' to .projenrc.json and run pj\\", + author_email=\\"TODO: Add 'authorAddress' to .projenrc.json and run pj\\", + license=\\"# TODO: Add 'license' to .projenrc.json and run pj\\", package_dir={SKILL_PKG: \\"\\"}, package_data={SKILL_PKG: find_resource_files()}, packages=[SKILL_PKG],