diff --git a/commands/alias.js b/commands/alias.js new file mode 100644 index 00000000..b9f00e5e --- /dev/null +++ b/commands/alias.js @@ -0,0 +1,110 @@ +import ora from 'ora'; +import semver from 'semver'; +import Alias from '../classes/alias.js'; +import { logger, getDefaults, getCWD } from '../utils/index.js'; +import { Alias as AliasFormatter } from '../formatters/index.js'; + +export const command = 'alias [name] [version] [alias]'; + +export const aliases = ['a']; + +export const describe = `Create or update a semver major alias for a package, NPM package or import map as identified by its name and version. A package with the given name and version must already exist on the Eik server. The alias should be the semver major part of the package version. Eg. for a package of version 5.4.3, you should use 5 as the alias. The alias type (npm, map, package) is detected from eik.json in the current working directory.`; + +export const builder = (yargs) => { + const cwd = getCWD(); + const defaults = getDefaults(cwd); + + yargs + .positional('name', { + describe: 'Name matching a package or import map on the Eik server', + type: 'string', + default: defaults.name, + }) + .positional('version', { + describe: 'The version the alias should redirect to', + type: 'string', + default: defaults.version, + }) + .positional('alias', { + describe: + 'Alias, should be the semver major component of version. Eg. 1.0.0 should be given the alias 1', + type: 'string', + default: defaults.version ? semver.major(defaults.version) : null, + }); + + yargs.options({ + server: { + alias: 's', + describe: 'Specify location of Eik asset server.', + default: defaults.server, + }, + cwd: { + alias: 'c', + describe: 'Alter the current working directory.', + default: defaults.cwd, + }, + type: { + describe: + 'Alter the alias type. Default is detected from eik.json. Valid values are `package`, `npm`, or `map` Eg. --type npm', + default: defaults.type, + }, + debug: { + describe: 'Logs additional messages', + default: false, + type: 'boolean', + }, + token: { + describe: + 'Provide a jwt token to be used to authenticate with the Eik server.', + default: '', + alias: 't', + }, + }); + + yargs.default('token', defaults.token, defaults.token ? '######' : ''); + + yargs.example(`eik alias my-app 1.0.0 1`); + yargs.example(`eik alias my-app 1.7.3 1`); + yargs.example(`eik alias my-app 6.3.1 6`); + yargs.example( + `eik alias my-app 6.3.1 6 --server https://assets.myeikserver.com`, + ); + yargs.example(`eik alias my-app 4.2.2 4 --debug`); + yargs.example(`eik alias my-app 4.2.2 4 --type package`); +}; + +export const handler = async (argv) => { + const spinner = ora({ stream: process.stdout }).start('working...'); + let success = false; + const { debug, server, type } = argv; + const log = logger(spinner, debug); + let af; + + try { + const data = await new Alias({ + type, + logger: log, + ...argv, + }).run(); + + // TODO: get rid of this rediculous formatter class idea that past me put here to irk present and future me. + // Smells like DRY silliness + af = new AliasFormatter(data); + + const createdOrUpdated = data.update ? 'Updated' : 'Created'; + log.info( + `${createdOrUpdated} alias for "${type}" "${data.name}". ("${data.version}" => "v${data.alias}")`, + ); + success = true; + } catch (err) { + log.warn(err.message); + } + + spinner.text = ''; + spinner.stopAndPersist(); + if (success) { + af?.format(server); + } else { + process.exit(1); + } +}; diff --git a/commands/index.js b/commands/index.js index 8e06465a..9d578bf3 100644 --- a/commands/index.js +++ b/commands/index.js @@ -9,6 +9,7 @@ import * as packageAlias from './package-alias.js'; import * as ping from './ping.js'; import * as publish from './publish.js'; import * as version from './version.js'; +import * as alias from './alias.js'; export const commands = [ init, @@ -22,4 +23,5 @@ export const commands = [ ping, publish, version, + alias, ]; diff --git a/commands/init.js b/commands/init.js index 79a4b14a..5b82815e 100644 --- a/commands/init.js +++ b/commands/init.js @@ -7,8 +7,7 @@ const command = 'init'; const aliases = ['i']; -const describe = `Creates a new default "eik.json" file and saves it to the current working directory - Override default "eik.json" fields using command line flags --server, --name, --major, --js and --css`; +const describe = `Creates a new default "eik.json" file and saves it to the current working directory. Override default "eik.json" fields using command line flags --server, --name, --major, --js and --css`; const builder = (yargs) => { yargs.example('eik init'); @@ -21,17 +20,12 @@ const builder = (yargs) => { yargs.options({ server: { alias: 's', - describe: `Specify asset server field in "eik.json". - This the URL to an Eik asset server - Eg. --server https://assets.myeikserver.com`, + describe: `Specify asset server field in "eik.json". This the URL to an Eik asset server Eg. --server https://assets.myeikserver.com`, default: '', }, cwd: { alias: 'c', - describe: `Alter the current working directory - Defaults to the directory where the command is being run. - This affects where the generated "eik.json" file will be saved. - Eg. --cwd /path/to/save/to`, + describe: `Alter the current working directory. Defaults to the directory where the command is being run. This affects where the generated "eik.json" file will be saved. Eg. --cwd /path/to/save/to`, default: process.cwd(), }, version: { diff --git a/commands/login.js b/commands/login.js index df2c06ad..26e02b8e 100644 --- a/commands/login.js +++ b/commands/login.js @@ -11,9 +11,7 @@ export const command = 'login'; export const aliases = []; -export const describe = `Authenticate against an Eik server and save the returned token to an .eikrc file in the users home directory. - You can specify key and server values to authenticate against using the --key and --server flags which will then bypass login prompts - It is possible to be authenticated against multiple asset servers simultaneously. Simply call "eik login" multiple times.`; +export const describe = `Authenticate against an Eik server and save the returned token to an .eikrc file in the users home directory. You can specify key and server values to authenticate against using the --key and --server flags which will then bypass login prompts. It is possible to be authenticated against multiple asset servers simultaneously. Simply call "eik login" multiple times.`; export const builder = (yargs) => { yargs.example('eik login --server https://assets.myserver.com'); @@ -28,20 +26,13 @@ export const builder = (yargs) => { yargs.options({ server: { alias: 's', - describe: `Eik server address - Specify location of the Eik asset server to authenticate against. - If an eik.json file is present in the current working directory, the files server value will be used as default. - If no eik.json file is present in the current working directory and this flag is not specified, a prompt will be presented to ask for the server address to be input - Eg. --server https://assets.myeikserver.com`, + describe: `Eik server address. Specify location of the Eik asset server to authenticate against. If an eik.json file is present in the current working directory, the files server value will be used as default. If no eik.json file is present in the current working directory and this flag is not specified, a prompt will be presented to ask for the server address to be input. Eg. --server https://assets.myeikserver.com`, type: 'string', default: defaults.server, }, key: { alias: 'k', - describe: `Login access key. - This is a passkey for a given user account and needs to be configured on the server. - If this flag is not specifed, a prompt will be used to ask for the key to be input. - Eg. --key ########`, + describe: `Login access key. This is a passkey for a given user account and needs to be configured on the server. If this flag is not specifed, a prompt will be used to ask for the key to be input. Eg. --key ########`, type: 'string', default: '', }, diff --git a/commands/map-alias.js b/commands/map-alias.js index 69a19657..d0b966c6 100644 --- a/commands/map-alias.js +++ b/commands/map-alias.js @@ -1,3 +1,5 @@ +// @deprecated in favor of `alias` command + import ora from 'ora'; import Alias from '../classes/alias.js'; import { logger, getDefaults, getCWD } from '../utils/index.js'; @@ -7,10 +9,7 @@ export const command = 'map-alias '; export const aliases = ['ma']; -export const describe = `Create a semver major alias for an import map as identified by its name and version. - An import map with the given name and version must already exist on asset server - Alias should be the semver major part of the import map version. - Eg. For an import map of version 5.4.3, you should use 5 as the alias`; +export const describe = `DEPRECATED: This command has been replaced by the alias command and will be removed in a future version. Create a semver major alias for an import map as identified by its name and version. An import map with the given name and version must already exist on asset server. Alias should be the semver major part of the import map version. Eg. For an import map of version 5.4.3, you should use 5 as the alias`; export const builder = (yargs) => { const cwd = getCWD(); @@ -100,3 +99,6 @@ export const handler = async (argv) => { process.exit(1); } }; + +export const deprecated = + '"map-alias" will be removed in a future version. Please use "alias" instead'; diff --git a/commands/map.js b/commands/map.js index f6d79a91..b7b50039 100644 --- a/commands/map.js +++ b/commands/map.js @@ -8,9 +8,7 @@ export const command = 'map '; export const aliases = ['m']; -export const describe = `Upload an import map file to the server under a given name and version. - A name/version combination must be unique and a version must be semver compliant. - Subsquent published versions must increase. Eg. 1.0.0 1.0.1, 1.1.0, 2.0.0 etc.`; +export const describe = `Upload an import map file to the server under a given name and version. A name/version combination must be unique and a version must be semver compliant. Subsquent published versions must increase. Eg. 1.0.0 1.0.1, 1.1.0, 2.0.0 etc.`; export const builder = (yargs) => { const cwd = getCWD(); diff --git a/commands/meta.js b/commands/meta.js index 10d056ee..f956194f 100644 --- a/commands/meta.js +++ b/commands/meta.js @@ -7,8 +7,7 @@ export const command = 'meta '; export const aliases = ['show']; -export const describe = `Retrieve meta information by package, map or npm name - If a given name exists in several types (package and map for example), results will be returned and displayed from all matching types`; +export const describe = `Retrieve meta information by package, map or npm name.If a given name exists in several types (package and map for example), results will be returned and displayed from all matching types`; export const builder = (yargs) => { const cwd = getCWD(); diff --git a/commands/npm-alias.js b/commands/npm-alias.js index f65f9105..c5e1f1cf 100644 --- a/commands/npm-alias.js +++ b/commands/npm-alias.js @@ -1,3 +1,5 @@ +// @deprecated in favor of `alias` command + import ora from 'ora'; import Alias from '../classes/alias.js'; import { logger, getDefaults, getCWD } from '../utils/index.js'; @@ -7,10 +9,7 @@ export const command = 'npm-alias '; export const aliases = ['na', 'dep-alias', 'dependency-alias']; -export const describe = `Create a semver major alias for an NPM package as identified by its name and version. - An NPM package with the given name and version must already exist on the asset server - Alias should be the semver major part of the NPM package version. - Eg. For an NPM package of version 5.4.3, you should use 5 as the alias`; +export const describe = `DEPRECATED: This command has been replaced by the alias command and will be removed in a future version. Create a semver major alias for an NPM package as identified by its name and version. An NPM package with the given name and version must already exist on the asset server. Alias should be the semver major part of the NPM package version. Eg. For an NPM package of version 5.4.3, you should use 5 as the alias`; export const builder = (yargs) => { const cwd = getCWD(); @@ -95,3 +94,6 @@ export const handler = async (argv) => { process.exit(1); } }; + +export const deprecated = + '"npm-alias" will be removed in a future version. Please use "alias" instead'; diff --git a/commands/package-alias.js b/commands/package-alias.js index e8a9d26b..265eed33 100644 --- a/commands/package-alias.js +++ b/commands/package-alias.js @@ -1,3 +1,5 @@ +// @deprecated in favor of `alias` command + import ora from 'ora'; import semver from 'semver'; import Alias from '../classes/alias.js'; @@ -8,10 +10,7 @@ export const command = 'package-alias [name] [version] [alias]'; export const aliases = ['pkg-alias', 'pa']; -export const describe = `Create a semver major alias for a package as identified by its name and version. - A package with the given name and version must already exist on asset server - Alias should be the semver major part of the package version. - Eg. For a package of version 5.4.3, you should use 5 as the alias`; +export const describe = `DEPRECATED: This command has been replaced by the alias command and will be removed in a future version. Create a semver major alias for a package as identified by its name and version. A package with the given name and version must already exist on asset server. Alias should be the semver major part of the package version. Eg. For a package of version 5.4.3, you should use 5 as the alias`; export const builder = (yargs) => { const cwd = getCWD(); @@ -99,8 +98,11 @@ export const handler = async (argv) => { spinner.text = ''; spinner.stopAndPersist(); if (success) { - af.format(server); + af?.format(server); } else { process.exit(1); } }; + +export const deprecated = + '"package-alias" will be removed in a future version. Please use "alias" instead'; diff --git a/commands/publish.js b/commands/publish.js index 77c0832d..a6e57542 100644 --- a/commands/publish.js +++ b/commands/publish.js @@ -44,8 +44,7 @@ export const builder = (yargs) => { type: 'boolean', }, token: { - describe: `Provide a jwt token to be used to authenticate with the Eik server. - Automatically determined if authenticated (via eik login)`, + describe: `Provide a jwt token to be used to authenticate with the Eik server. Automatically determined if authenticated (via eik login)`, type: 'string', alias: 't', }, diff --git a/test/integration/alias-legacy.test.mjs b/test/integration/alias-legacy.test.mjs new file mode 100644 index 00000000..e09ea608 --- /dev/null +++ b/test/integration/alias-legacy.test.mjs @@ -0,0 +1,233 @@ +import fastify from 'fastify'; +import { promises as fs } from 'node:fs'; +import os from 'node:os'; +import { exec as execCallback } from 'child_process'; +import { join, basename } from 'node:path'; +import { test, beforeEach, afterEach } from 'tap'; +import EikService from '@eik/service'; +import Sink from '@eik/sink-memory'; +import cli from '../../classes/index.js'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +function exec(cmd) { + return new Promise((resolve) => { + execCallback(cmd, (error, stdout, stderr) => { + resolve({ error, stdout, stderr }); + }); + }); +} + +beforeEach(async (t) => { + const server = fastify({ logger: false }); + const memSink = new Sink(); + const service = new EikService({ customSink: memSink }); + server.register(service.api()); + const address = await server.listen({ + host: '127.0.0.1', + port: 0, + }); + const folder = await fs.mkdtemp(join(os.tmpdir(), basename(__filename))); + const eik = join(__dirname, '../../index.js'); + + const token = await cli.login({ + server: address, + key: 'change_me', + }); + + const assets = { + name: 'scroll-into-view-if-needed', + version: '2.2.24', + type: 'npm', + server: address, + files: { + 'index.js': join(__dirname, './../fixtures/client.js'), + 'index.css': join(__dirname, './../fixtures/styles.css'), + }, + }; + + await fs.writeFile(join(folder, 'eik.json'), JSON.stringify(assets)); + + const cmd = `${eik} package --token ${token} --cwd ${folder}`; + await exec(cmd); + + const map = { + imports: { + 'scroll-into-view-if-needed': new URL( + '/npm/scroll-into-view-if-needed/2.2.24/index.js', + address, + ).href, + }, + }; + await fs.writeFile(join(folder, 'import-map.json'), JSON.stringify(map)); + const mapCmd = `${eik} map test-map 1.0.0 import-map.json + --token ${token} + --server ${address} + --cwd ${folder}`; + await exec(mapCmd.split('\n').join(' ')); + + t.context.server = server; + t.context.address = address; + t.context.folder = folder; + t.context.token = token; +}); + +afterEach(async (t) => { + await t.context.server.close(); +}); + +test('eik package-alias ', async (t) => { + const { address, token, folder: cwd } = t.context; + const eik = join(__dirname, '../../index.js'); + + const assets = { + server: address, + name: 'my-pack', + version: '1.0.0', + files: { + 'index.js': join(__dirname, '../fixtures/client.js'), + 'index.css': join(__dirname, '../fixtures/styles.css'), + }, + }; + + await fs.writeFile(join(cwd, 'eik.json'), JSON.stringify(assets)); + + const cmd1 = `${eik} package --token ${token} --cwd ${cwd}`; + await exec(cmd1); + + const cmd2 = `${eik} package-alias my-pack 1.0.0 1 + --token ${token} + --server ${address} + --cwd ${cwd}`; + + const { error, stdout } = await exec(cmd2.split('\n').join(' ')); + + const res = await fetch(new URL('/pkg/my-pack/v1/index.js', address)); + + t.equal(res.ok, true); + t.notOk(error); + t.match(stdout, 'PACKAGE'); + t.match(stdout, 'my-pack'); + t.match(stdout, '1.0.0'); + t.match(stdout, 'v1'); + t.match(stdout, 'NEW'); +}); + +test('eik npm-alias --token --server : no eik.json or .eikrc', async (t) => { + const eik = join(__dirname, '../../index.js'); + const cmd = `${eik} npm-alias scroll-into-view-if-needed 2.2.24 2 + --token ${t.context.token} + --server ${t.context.address} + --cwd ${t.context.folder}`; + + const { error, stdout } = await exec(cmd.split('\n').join(' ')); + + const res = await fetch( + new URL( + '/npm/scroll-into-view-if-needed/v2/index.js', + t.context.address, + ), + ); + + t.equal(res.ok, true); + t.notOk(error); + t.match(stdout, 'NPM'); + t.match(stdout, 'scroll-into-view-if-needed'); + t.match(stdout, '2.2.24'); + t.match(stdout, 'v2'); + t.match(stdout, 'NEW'); + t.end(); +}); + +test('eik npm-alias : publish details provided by eik.json file', async (t) => { + const assets = { + name: 'test-app', + version: '1.0.0', + server: t.context.address, + files: { + 'index.js': join(__dirname, './../fixtures/client.js'), + 'index.css': join(__dirname, './../fixtures/styles.css'), + }, + }; + await fs.writeFile( + join(t.context.folder, 'eik.json'), + JSON.stringify(assets), + ); + const eik = join(__dirname, '../../index.js'); + const cmd = `${eik} npm-alias scroll-into-view-if-needed 2.2.24 2 --token ${t.context.token} --cwd ${t.context.folder}`; + + const { error, stdout } = await exec(cmd); + + const res = await fetch( + new URL( + '/npm/scroll-into-view-if-needed/v2/index.js', + t.context.address, + ), + ); + + t.equal(res.ok, true); + t.notOk(error); + t.match(stdout, 'NPM'); + t.match(stdout, 'scroll-into-view-if-needed'); + t.match(stdout, '2.2.24'); + t.match(stdout, 'v2'); + t.match(stdout, 'NEW'); + t.end(); +}); + +test('eik map-alias --token --server : no eik.json or .eikrc', async (t) => { + const eik = join(__dirname, '../../index.js'); + const cmd = `${eik} map-alias test-map 1.0.0 1 + --token ${t.context.token} + --server ${t.context.address} + --cwd ${t.context.folder}`; + + const { error, stdout } = await exec(cmd.split('\n').join(' ')); + + const res = await fetch(new URL('/map/test-map/v1', t.context.address)); + + t.equal(res.ok, true); + + t.notOk(error); + t.match(stdout, 'MAP'); + t.match(stdout, 'test-map'); + t.match(stdout, '1.0.0'); + t.match(stdout, 'v1'); + t.match(stdout, 'NEW'); + t.end(); +}); + +test('eik map-alias : publish details provided by eik.json file', async (t) => { + const assets = { + name: 'test-app', + version: '1.0.0', + server: t.context.address, + files: { + 'index.js': join(__dirname, './../fixtures/client.js'), + 'index.css': join(__dirname, './../fixtures/styles.css'), + }, + }; + await fs.writeFile( + join(t.context.folder, 'eik.json'), + JSON.stringify(assets), + ); + const eik = join(__dirname, '../../index.js'); + const cmd = `${eik} map-alias test-map 1.0.0 1 --token ${t.context.token} --cwd ${t.context.folder}`; + + const { error, stdout } = await exec(cmd); + + const res = await fetch(new URL('/map/test-map/v1', t.context.address)); + + t.equal(res.ok, true); + + t.notOk(error); + t.match(stdout, 'MAP'); + t.match(stdout, 'test-map'); + t.match(stdout, '1.0.0'); + t.match(stdout, 'v1'); + t.match(stdout, 'NEW'); + t.end(); +}); diff --git a/test/integration/alias.test.mjs b/test/integration/alias.test.mjs index e09ea608..af5a5f0e 100644 --- a/test/integration/alias.test.mjs +++ b/test/integration/alias.test.mjs @@ -1,6 +1,6 @@ import fastify from 'fastify'; import { promises as fs } from 'node:fs'; -import os from 'node:os'; +import os, { type } from 'node:os'; import { exec as execCallback } from 'child_process'; import { join, basename } from 'node:path'; import { test, beforeEach, afterEach } from 'tap'; @@ -79,12 +79,13 @@ afterEach(async (t) => { await t.context.server.close(); }); -test('eik package-alias ', async (t) => { +test('packages: eik alias ', async (t) => { const { address, token, folder: cwd } = t.context; const eik = join(__dirname, '../../index.js'); const assets = { server: address, + type: 'package', name: 'my-pack', version: '1.0.0', files: { @@ -98,7 +99,7 @@ test('eik package-alias ', async (t) => { const cmd1 = `${eik} package --token ${token} --cwd ${cwd}`; await exec(cmd1); - const cmd2 = `${eik} package-alias my-pack 1.0.0 1 + const cmd2 = `${eik} alias my-pack 1.0.0 1 --token ${token} --server ${address} --cwd ${cwd}`; @@ -116,10 +117,11 @@ test('eik package-alias ', async (t) => { t.match(stdout, 'NEW'); }); -test('eik npm-alias --token --server : no eik.json or .eikrc', async (t) => { +test('npm: eik alias --token --server : no eik.json or .eikrc', async (t) => { const eik = join(__dirname, '../../index.js'); const cmd = `${eik} npm-alias scroll-into-view-if-needed 2.2.24 2 --token ${t.context.token} + --type npm --server ${t.context.address} --cwd ${t.context.folder}`; @@ -142,9 +144,10 @@ test('eik npm-alias --token --server : no eik.json or . t.end(); }); -test('eik npm-alias : publish details provided by eik.json file', async (t) => { +test('npm: eik alias : publish details provided by eik.json file', async (t) => { const assets = { name: 'test-app', + type: 'npm', version: '1.0.0', server: t.context.address, files: { @@ -157,7 +160,7 @@ test('eik npm-alias : publish details provided by eik.j JSON.stringify(assets), ); const eik = join(__dirname, '../../index.js'); - const cmd = `${eik} npm-alias scroll-into-view-if-needed 2.2.24 2 --token ${t.context.token} --cwd ${t.context.folder}`; + const cmd = `${eik} alias scroll-into-view-if-needed 2.2.24 2 --token ${t.context.token} --cwd ${t.context.folder}`; const { error, stdout } = await exec(cmd); @@ -178,10 +181,11 @@ test('eik npm-alias : publish details provided by eik.j t.end(); }); -test('eik map-alias --token --server : no eik.json or .eikrc', async (t) => { +test('map: eik alias --token --server : no eik.json or .eikrc', async (t) => { const eik = join(__dirname, '../../index.js'); const cmd = `${eik} map-alias test-map 1.0.0 1 --token ${t.context.token} + --type map --server ${t.context.address} --cwd ${t.context.folder}`; @@ -200,9 +204,10 @@ test('eik map-alias --token --server : no eik.json or . t.end(); }); -test('eik map-alias : publish details provided by eik.json file', async (t) => { +test('map: eik alias : publish details provided by eik.json file', async (t) => { const assets = { name: 'test-app', + type: 'map', version: '1.0.0', server: t.context.address, files: { @@ -215,7 +220,7 @@ test('eik map-alias : publish details provided by eik.j JSON.stringify(assets), ); const eik = join(__dirname, '../../index.js'); - const cmd = `${eik} map-alias test-map 1.0.0 1 --token ${t.context.token} --cwd ${t.context.folder}`; + const cmd = `${eik} alias test-map 1.0.0 1 --token ${t.context.token} --cwd ${t.context.folder}`; const { error, stdout } = await exec(cmd);