Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@W-11920099 Re-write 'npm push' in Typescript, warn if Node deprecated #763

Merged
merged 40 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
94e761e
Re-write 'push' in TS, support multiple credentials files.
olibrook Oct 7, 2022
c4e517e
Update packages/pwa-kit-dev/src/utils/upload2.ts
olibrook Oct 17, 2022
cb4a5d3
Update packages/pwa-kit-dev/src/utils/upload2.ts
olibrook Oct 17, 2022
b66e5f7
Update packages/pwa-kit-dev/src/utils/upload2.ts
olibrook Oct 17, 2022
727c651
WIP
olibrook Oct 17, 2022
1966b88
Fix test, format
olibrook Oct 17, 2022
64123e8
Lint
olibrook Oct 17, 2022
fe79134
Try and fix windows test
olibrook Oct 17, 2022
8133b83
Snyk
olibrook Oct 17, 2022
47ae4e3
Fix conflicts
olibrook Jan 25, 2023
6d1767b
Fix typo in format command
olibrook Jan 26, 2023
83f3f39
Use new API client for Cloud in tail-logs command
olibrook Jan 26, 2023
7b5ec0e
Fix tail-logs command
olibrook Jan 26, 2023
1369a16
Lint
olibrook Jan 26, 2023
3355a36
Merge branch 'develop' into warn-on-outdated-node
olibrook Jan 26, 2023
2cd0630
Merge branch 'develop' into warn-on-outdated-node
olibrook Jan 26, 2023
d37f0f1
Add types for the parseLog fn
olibrook Jan 26, 2023
bae2c21
Tidy
olibrook Jan 26, 2023
ba62bb4
Whoops, forgot to rename import in tests
olibrook Jan 26, 2023
104a527
Renane e -> err – will's feedback
olibrook Jan 26, 2023
d86e65e
Better Explain 'Must default to undefined' for --credentialsFile – wi…
olibrook Jan 26, 2023
b207647
Better docs for credentialsLocationDisplay
olibrook Jan 26, 2023
d093c84
Document scriptUtils conditional import – will's feedback
olibrook Jan 26, 2023
d903d66
Explain empty file – will's feedback
olibrook Jan 26, 2023
a7017c8
Way nicer test pattern – thanks Will!
olibrook Jan 26, 2023
6a7745b
Lint
olibrook Jan 26, 2023
21c945c
Merge branch 'warn-on-outdated-node' of https://github.com/Salesforce…
olibrook Jan 26, 2023
bf79c16
Fix handling of json/text error messages coming from Cloud.
olibrook Jan 26, 2023
cd90f2b
Lint
olibrook Jan 26, 2023
3d6e7ad
Merge branch 'warn-on-outdated-node' of https://github.com/Salesforce…
olibrook Jan 26, 2023
19b6e55
Fixup Will's suggested test pattern
olibrook Jan 26, 2023
226e82b
Don't randomly shuffle in test body
olibrook Jan 26, 2023
3d8b7e1
Delete all usages of assert.
olibrook Jan 26, 2023
a8d8cd0
Fix api key validation
olibrook Jan 26, 2023
c47009c
Use fs-extra to read json
olibrook Jan 26, 2023
69e0b3f
Update getHeaders with Will's suggestion
olibrook Jan 26, 2023
f9ac132
Nicer error message
olibrook Jan 26, 2023
b7da4e7
Switch to os.homedir
olibrook Jan 26, 2023
f5d0c6d
Switch to os.homedir
olibrook Jan 26, 2023
9aaa86f
Merge branch 'develop' into warn-on-outdated-node
olibrook Jan 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 115 additions & 94 deletions packages/pwa-kit-dev/bin/pwa-kit-dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,39 @@ const WebSocket = require('ws')
const program = require('commander')
const validator = require('validator')
const {execSync: _execSync} = require('child_process')
const scriptUtils = require('../scripts/utils')
const uploadBundle = require('../scripts/upload.js')
const pkg = require('../package.json')
const {getConfig} = require('pwa-kit-runtime/utils/ssr-config')

const scriptUtils = (() => {
olibrook marked this conversation as resolved.
Show resolved Hide resolved
try {
return require('../dist/utils/script-utils')
} catch {
return require('../utils/script-utils')
}
olibrook marked this conversation as resolved.
Show resolved Hide resolved
})()

const colors = {
info: 'green',
warn: 'yellow',
error: 'red'
error: 'red',
success: 'cyan'
}

const fancyLog = (level, msg) => {
const color = colors[level] || 'green'
const colorFn = chalk[color]
console.log(`${colorFn(level)}: ${msg}`)
}
const info = (msg) => fancyLog('info', msg)
const success = (msg) => fancyLog('success', msg)
const warn = (msg) => fancyLog('warn', msg)
const error = (msg) => fancyLog('error', msg)

const execSync = (cmd, opts) => {
const defaults = {stdio: 'inherit'}
return _execSync(cmd, {...defaults, ...opts})
}

const main = () => {
const main = async () => {
const pkgRoot = p.join(__dirname, '..')
process.env.CONTEXT = process.cwd()

Expand Down Expand Up @@ -65,6 +81,11 @@ const main = () => {
].join('\n')
)

const credentialsLocationDisplay = () => {
const dir = process.platform === 'win32' ? '%USERPROFILE%' : '~'
olibrook marked this conversation as resolved.
Show resolved Hide resolved
return p.join(dir, '.mobify')
}

/**
* All Managed Runtime commands take common opts like --cloud-origin
* and --credentialsFile. These are set to be split out from the SDK
Expand All @@ -75,36 +96,26 @@ const main = () => {
.command(name)
.addOption(
new program.Option('--cloud-origin <origin>', 'the API origin to connect to')
.default('https://cloud.mobify.com')
.default(scriptUtils.DEFAULT_CLOUD_ORIGIN)
.env('CLOUD_API_BASE')
.argParser((val) => {
try {
const url = new URL(val)
const labels = url.host.split('.')
if (
labels.length !== 3 ||
!labels[0].startsWith('cloud') ||
!labels[1].startsWith('mobify') ||
labels[2] !== 'com'
) {
throw new Error()
}
} catch {
throw new program.InvalidArgumentError(
`'${val}' is not a valid Cloud origin`
)
}
return val
})
)
.addOption(
new program.Option(
'-c, --credentialsFile <credentialsFile>',
'override the standard credentials file location'
`override the standard credentials file location "${credentialsLocationDisplay()}"`
)
.default(scriptUtils.getCredentialsFile())
.default(undefined) // *must* default to undefined!
olibrook marked this conversation as resolved.
Show resolved Hide resolved
.env('PWA_KIT_CREDENTIALS_FILE')
)
.hook('preAction', (thisCommand, actionCommand) => {
// The final credentialsFile path depends on both cloudOrigin and credentialsFile opts.
// Pre-process before passing to the command.
const {cloudOrigin, credentialsFile} = actionCommand.opts()
actionCommand.setOptionValue(
'credentialsFile',
scriptUtils.getCredentialsFile(cloudOrigin, credentialsFile)
)
})
}

managedRuntimeCommand('save-credentials')
Expand All @@ -124,21 +135,20 @@ const main = () => {
'-k, --key <api-key>',
`find your API key at https://runtime.commercecloud.com/account/settings`,
(val) => {
if (!(typeof val === 'string' && val.length > 0)) {
throw new program.InvalidArgumentError(`"${val}" cannot be empty`)
if (!(typeof val === 'string') && val.length > 0) {
olibrook marked this conversation as resolved.
Show resolved Hide resolved
throw new program.InvalidArgumentError(`"api-key" cannot be empty`)
} else {
return val
}
}
)
.action(({user, key, credentialsFile}) => {
.action(async ({user, key, credentialsFile}) => {
try {
fse.writeJson(credentialsFile, {username: user, api_key: key}, {spaces: 4})
console.log(`Saved Managed Runtime credentials to "${credentialsFile}".`)
success(`Saved Managed Runtime credentials to "${chalk.cyan(credentialsFile)}".`)
} catch (e) {
console.error('Failed to save credentials.')
console.error(e)
process.exit(1)
error('Failed to save credentials.')
throw e
}
})

Expand All @@ -149,7 +159,7 @@ const main = () => {
new program.Option('--inspect', 'enable debugging with --inspect on the node process')
)
.addOption(new program.Option('--noHMR', 'disable the client-side hot module replacement'))
.action(({inspect, noHMR}) => {
.action(async ({inspect, noHMR}) => {
execSync(
`node${inspect ? ' --inspect' : ''} ${p.join(process.cwd(), 'app', 'ssr.js')}`,
{
Expand All @@ -172,7 +182,7 @@ const main = () => {
.env('PWA_KIT_BUILD_DIR')
)
.description(`build your app for production`)
.action(({buildDirectory}) => {
.action(async ({buildDirectory}) => {
const webpack = p.join(require.resolve('webpack'), '..', '..', '..', '.bin', 'webpack')
const projectWebpack = p.join(process.cwd(), 'webpack.config.js')
const webpackConf = fse.pathExistsSync(projectWebpack)
Expand Down Expand Up @@ -241,50 +251,63 @@ const main = () => {
'immediately deploy the bundle to this target once it is pushed'
)
)
.action(({buildDirectory, message, projectSlug, target, cloudOrigin, credentialsFile}) => {
// Set the deployment target env var, this is required to ensure we
// get the correct configuration object.
process.env.DEPLOY_TARGET = target
const mobify = getConfig() || {}

if (!projectSlug) {
projectSlug = scriptUtils.readPackageJson('name')
}

const options = {
.action(
async ({
buildDirectory,
// Avoid setting message if it's blank, so that it doesn't override the default
...(message ? {message} : undefined),
message,
projectSlug,
target,
credentialsFile,
// Note: Cloud expects snake_case, but package.json uses camelCase.
ssr_parameters: mobify.ssrParameters,
ssr_only: mobify.ssrOnly,
ssr_shared: mobify.ssrShared,
set_ssr_values: true,
origin: cloudOrigin
}
cloudOrigin,
credentialsFile
}) => {
// Set the deployment target env var, this is required to ensure we
// get the correct configuration object.
process.env.DEPLOY_TARGET = target

const credentials = await scriptUtils.readCredentials(credentialsFile)

const mobify = getConfig() || {}

if (!projectSlug) {
try {
const projectPkg = p.join(process.cwd(), 'package.json')
const {name} = fse.readJsonSync(projectPkg)
if (!name) throw new Error(`Missing "name" field in ${projectPkg}`)
projectSlug = name
} catch (err) {
throw new Error(
`Could not detect project slug from "name" field in package.json: ${err.message}`
)
}
}

const bundle = await scriptUtils.createBundle({
message,
ssr_parameters: mobify.ssrParameters,
ssr_only: mobify.ssrOnly,
ssr_shared: mobify.ssrShared,
buildDirectory,
projectSlug
})
const client = new scriptUtils.CloudAPIClient({
credentials,
origin: cloudOrigin
})

if (
!Array.isArray(options.ssr_only) ||
options.ssr_only.length === 0 ||
!Array.isArray(options.ssr_shared) ||
options.ssr_shared.length === 0
) {
scriptUtils.fail('ssrEnabled is set, but no ssrOnly or ssrShared files are defined')
info(`Beginning upload to ${cloudOrigin}`)
const data = await client.push(bundle, projectSlug, target)
const warnings = data.warnings || []
warnings.forEach(warn)
success('Bundle Uploaded')
}
uploadBundle(options).catch((err) => {
console.error(err.message || err)
})
})
)

program
.command('lint')
.description('lint all source files')
.argument('<path>', 'path or glob to lint')
.option('--fix', 'Try and fix errors (default: false)')
.action((path, {fix}) => {
.action(async (path, {fix}) => {
const eslint = p.join(require.resolve('eslint'), '..', '..', '..', '.bin', 'eslint')
const eslintConfig = p.join(__dirname, '..', 'configs', 'eslint', 'eslint-config.js')
execSync(
Expand All @@ -298,15 +321,15 @@ const main = () => {
.command('format')
.description('automatically re-format all source files')
.argument('<path>', 'path or glob to format')
.action((path) => {
.action(async (path) => {
const prettier = p.join(require.resolve('prettier'), '..', '..', '.bin', 'prettier')
execSync(`${prettier} --write "${path}"`)
})

program
.command('test')
.description('test the project')
.action((_, {args}) => {
.action(async (_, {args}) => {
const jest = p.join(require.resolve('jest'), '..', '..', '..', '.bin', 'jest')
execSync(
`${jest} --passWithNoTests --maxWorkers=2${args.length ? ' ' + args.join(' ') : ''}`
Expand All @@ -322,24 +345,20 @@ const main = () => {
)
)
.requiredOption('-e, --environment <environmentSlug>', 'the environment slug')
.action(async ({project, environment, cloudOrigin, credentialsFile}, command) => {
.action(async ({project, environment, cloudOrigin, credentialsFile}) => {
if (!project) {
project = scriptUtils.readPackageJson('name')
project = scriptUtils.getPkgJSON()['name']
}

let credentials
try {
credentials = fse.readJsonSync(credentialsFile)
} catch (e) {
scriptUtils.fail(`Error reading credentials: ${e}`)
}
const credentials = await scriptUtils.readCredentials(credentialsFile)

const client = new scriptUtils.CloudAPIClient({
credentials,
origin: cloudOrigin
})

const token = await client.createLoggingToken(project, environment)

const token = await scriptUtils.createToken(
project,
environment,
cloudOrigin,
credentials.api_key
)
const url = new URL(cloudOrigin.replace('cloud', 'logs'))
url.protocol = 'wss'
url.search = new URLSearchParams({
Expand All @@ -363,9 +382,10 @@ const main = () => {
console.log('Connection closed with code', code)
})

ws.on('error', (error) => {
ws.on('error', (e) => {
olibrook marked this conversation as resolved.
Show resolved Hide resolved
clearInterval(heartbeat)
scriptUtils.fail(`Error tailing logs: ${error.message}`)
error(`Error tailing logs: ${e.message}`)
throw e
})

ws.on('message', (data) => {
Expand All @@ -386,7 +406,6 @@ const main = () => {
})

// Global options

program.option('-v, --version', 'show version number').action(({version}) => {
if (version) {
console.log(pkg.version)
Expand All @@ -395,12 +414,14 @@ const main = () => {
}
})

program.parse(process.argv)
await program.parseAsync(process.argv)
}

Promise.resolve()
.then(() => main())
.catch((err) => {
console.error(err.message)
Promise.resolve().then(async () => {
try {
await main()
} catch (err) {
error(err.message || err.toString())
process.exit(1)
})
}
})
Loading