diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 8e444ec..c0cc990 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -44,7 +44,7 @@ runs: - name: Authenticate Artifact Registry if: steps.dependencies.outputs.authenticate == 'true' shell: bash - run: npm run auth + run: npx --yes google-artifactregistry-auth@latest env: GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }} diff --git a/.github/actions/publish/parse-package-json.sh b/.github/actions/publish/parse-package-json.sh index 7473a2b..96e6010 100755 --- a/.github/actions/publish/parse-package-json.sh +++ b/.github/actions/publish/parse-package-json.sh @@ -23,6 +23,13 @@ else if [[ "${PACKAGE_SCOPE}" = @sknups ]] ; then + DEPENDENCIES=$(jq -r ' (.devDependencies // {}, .dependencies // {} ) | keys[]' package.json) + if grep -q "@sknups-internal/" <<< "$DEPENDENCIES" ; then + LINE=$(grep -n '"@sknups-internal/:' package.json | cut -d: -f1 | head -n 1) + echo "::error file=package.json,line=${LINE}::Packages with scope @sknups cannot depend on packages with scope @sknups-internal" >> /dev/stderr + exit $ERROR + fi + echo "action=publish" >> "$GITHUB_OUTPUT" echo "scope=@sknups" >> "$GITHUB_OUTPUT" echo "npm package will be published to the PUBLIC npm registry" diff --git a/build.sh b/build.sh index 11eb6a3..dc37c04 100755 --- a/build.sh +++ b/build.sh @@ -97,7 +97,7 @@ function build { if [[ "${OFFLINE}" == "false" ]] ; then echo "Authenticating npm..." - npm run auth --silent || error + npx --yes google-artifactregistry-auth@latest --silent || error echo "Installing npm dependencies..." npm install --silent || error diff --git a/index.js b/index.js index e8cb8af..3589c74 100644 --- a/index.js +++ b/index.js @@ -54,29 +54,6 @@ async function execute (command) { } -/** - * Modifies the specified object by setting a value at the specified key. - * Equivalent to: - * - * data[key] = value; // if key is a string - * - * data[key[0]][key[1]] = value; // if key is an array - * - * @param {object} data - * @param {string|string[]} key - * @param {string|boolean} value - * @returns {object} - */ -function set (data, key, value) { - const selector = Array.isArray(key) ? key : [key]; - let selected = data; - while (selector.length > 1) { - selected = selected[selector.shift()]; - } - selected[selector[0]] = value; - return data; -} - /** * [over]write a key-value pair in the package.json file * @@ -85,9 +62,34 @@ function set (data, key, value) { * @returns {Promise} */ async function writePackageJson (key, value) { + + /** + * Modifies the specified object by setting a value at the specified key. + * Equivalent to: + * + * data[key] = value; // if key is a string + * + * data[key[0]][key[1]] = value; // if key is an array + * + * @param {object} data + * @param {string|string[]} key + * @param {string|boolean} value + * @returns {object} + */ + function set (data, key, value) { + const selector = Array.isArray(key) ? key : [key]; + let selected = data; + while (selector.length > 1) { + selected = selected[selector.shift()]; + } + selected[selector[0]] = value; + return data; + } + const data = JSON.parse(await readFile(PACKAGE_JSON, ENCODING)); set(data, key, value); await writeFile(PACKAGE_JSON, JSON.stringify(data, null, 2), ENCODING); + } /** @@ -117,28 +119,72 @@ ${variable} = [ `); } -async function main () { +class Git { - // https://github.com/sknups/drop-links.git - const url = (await execute('git config --get remote.origin.url')); + /** + * @param {string} url https://github.com/sknups/drop-links.git + */ + constructor (url) { + this._url = url; + } - // sknups/drop-links - const repository = url.replace('https://github.com/', '').replace('.git', ''); + static async initialize () { + return new Git(await execute('git config --get remote.origin.url')); + } - // sknups - const organisation = repository.split('/')[0]; + /** + * @returns {string} https://github.com/sknups/drop-links.git + */ + get url () { + return this._url; + } - // drop-links - const name = repository.split('/')[1]; + /** + * @returns {string} sknups/drop-links + */ + get repository () { + return this.url.replace('https://github.com/', '').replace('.git', ''); + } - // noinspection JSCheckFunctionSignatures - let answers = await inquirer.prompt([ + /** + * @returns {string} sknups + */ + get organisation () { + return this.repository.split('/')[0]; + } + + /** + * @returns {string} drop-links + */ + get name () { + return this.repository.split('/')[1]; + + } + +} + +/** + * @param {Git} git + * @returns {Promise} + */ +async function askProjectName (git) { + const answers = await inquirer.prompt([ { type: 'input', name: 'name', - default: name, + default: git.name, message: 'What is the project name?' - }, + } + + ]); + return answers.name; +} + +/** + * @returns {Promise<'script'|'library'>} + */ +async function askProjectNature () { + const answers = await inquirer.prompt([ { type: 'list', name: 'nature', @@ -151,85 +197,125 @@ async function main () { } ]); + return answers.nature; +} + +/** + * @returns {Promise} + */ +async function askIfDependsOnInternalPackages () { + const answers = await inquirer.prompt([ + { + type: 'list', + name: 'depends', + default: 0, + message: 'Do you depend on [other] internal packages?', + choices: [ + { value: false, name: 'no' }, + { value: true, name: 'yes' } + ], + } + ]); + return answers.depends; +} + +/** + * @returns {Promise<'@sknups'|'@sknups-internal'>} + */ +async function askProjectScope () { + const answers = await inquirer.prompt([ + { + type: 'list', + name: 'scope', + default: 0, + message: 'Are you publishing to the public internet?', + choices: [ + { value: '@sknups-internal', name: 'no, internal' }, + { value: '@sknups', name: 'yes, public' } + ], + } + ]); + return answers.scope; +} + +async function authenticateLocalUser () { + console.log(); + console.log('Running "npx --yes google-artifactregistry-auth@latest" to authorize downloads from the npm-internal registry...'); + await execute('npx --yes google-artifactregistry-auth@latest'); +} + +/** + * @param {Git} git + * @param {string} name + * @returns {Promise} + */ +async function handleScript (git, name) { + + await writePackageJson(['repository', 'url'], `git+${git.url}`); + await writePackageJson('private', true); + await writeIndexJs(TEMPLATE_INDEX_JS_SCRIPT); + + if (git.organisation !== 'sknups') { + await writePackageJson('name', `@${git.organisation}/${name}`); + return; + } + + // all scripts are notionally scoped to @sknups-internal, + // even though a package will not be published to npm registry + await writePackageJson('name', `@sknups-internal/${name}`); + + if (await askIfDependsOnInternalPackages()) { + await authenticateLocalUser(); + printTerraformMessage('npm_internal_reader_repositories', git.repository); + } + +} + +/** + * @param {Git} git + * @param {string} name + * @returns {Promise} + */ +async function handleLibrary (git, name) { + + await writePackageJson(['repository', 'url'], `git+${git.url}`); + await writePackageJson('private', false); + await writeIndexJs(TEMPLATE_INDEX_JS_LIBRARY); + + if (git.organisation !== 'sknups') { + await writePackageJson('name', `@${git.organisation}/${name}`); + return; + } + + const scope = await askProjectScope(); + await writePackageJson('name', `${scope}/${name}`); + + if (scope === '@sknups-internal') { + if (await askIfDependsOnInternalPackages()) { + await authenticateLocalUser(); + } + printTerraformMessage('npm_internal_writer_repositories', git.repository); + } + + if (scope === '@sknups') { + printTerraformMessage('npm_public_writer_repositories', git.repository); + } + +} + +async function main () { + + const git = await Git.initialize(); + + const name = await askProjectName(git); + const nature = await askProjectNature(); + + if (nature === 'script') { + await handleScript(git, name); + } - switch (answers.nature) { - case 'script': - await writePackageJson(['repository', 'url'], `git+${url}`); - await writePackageJson('private', true); - await writeIndexJs(TEMPLATE_INDEX_JS_SCRIPT); - if (organisation === 'sknups') { - - // all scripts are notionally scoped to @sknups-internal - await writePackageJson('name', `@sknups-internal/${answers.name}`); - answers = await inquirer.prompt([ - { - type: 'list', - name: 'depend', - default: 0, - message: 'Do you depend on internal packages?', - choices: [ - { value: false, name: 'no' }, - { value: true, name: 'yes' } - ], - } - ], answers); - if (answers.depend) { - console.log(); - console.log('Running "npm run auth" to authorize downloads from the npm-internal registry...'); - await execute('npm run auth'); - printTerraformMessage('npm_internal_reader_repositories', repository); - } - - } else { - await writePackageJson('name', `@${organisation}/${answers.name}`); - } - break; - case 'library': - await writePackageJson(['repository', 'url'], `git+${url}`); - await writePackageJson('private', false); - await writeIndexJs(TEMPLATE_INDEX_JS_LIBRARY); - if (organisation === 'sknups') { - answers = await inquirer.prompt([ - { - type: 'list', - name: 'scope', - default: 0, - message: 'Are you publishing to the public internet?', - choices: [ - { value: '@sknups-internal', name: 'no, internal' }, - { value: '@sknups', name: 'yes, public' } - ], - } - ], answers); - switch (answers.scope) { - case '@sknups-internal': - await writePackageJson('name', `@sknups-internal/${answers.name}`); - answers = await inquirer.prompt([ - { - type: 'list', - name: 'depend', - default: 0, - message: 'Do you depend on [other] internal packages?', - choices: [ - { value: false, name: 'no' }, - { value: true, name: 'yes' } - ], - } - ], answers); - if (answers.depend) { - console.log(); - console.log('Running "npm run auth" to authorize downloads from the npm-internal registry...'); - await execute('npm run auth'); - } - printTerraformMessage('npm_internal_writer_repositories', repository); - break; - case '@sknups': - await writePackageJson('name', `@sknups/${answers.name}`); - printTerraformMessage('npm_public_writer_repositories', repository); - break; - } - } - break; + if (nature === 'library') { + await handleLibrary(git, name); } } diff --git a/package-lock.json b/package-lock.json index 88596e2..61cbab6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "dependencies": { "inquirer": "^9.2.16" }, + "bin": { + "configure-javascript-template": "index.js" + }, "devDependencies": { "@sknups/eslint-config": "~1.3.0", "@types/inquirer": "^9.0.7", diff --git a/package.json b/package.json index 56d3d34..9e4c0f7 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "author": "SKNUPS", "type": "module", "exports": "./index.js", + "bin": "./index.js", "files": [ "index.js" ], - "bin": "./index.js", "scripts": { "auth": "npx google-artifactregistry-auth@latest", "compile": "npx tsc -p ./tsconfig.json",