-
Notifications
You must be signed in to change notification settings - Fork 174
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(examples): Update examples/react-native to use the new starter …
…template (#608)
- Loading branch information
Showing
14 changed files
with
909 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
# Docker images for the local stack | ||
export POSTGRESQL_IMAGE=postgres:14-alpine | ||
export ELECTRIC_IMAGE=electricsql/electric:latest | ||
export APP_NAME=react-native | ||
export APP_NAME=react_native | ||
export ELECTRIC_PORT=5133 | ||
export ELECTRIC_PROXY_PORT=65432 | ||
export ELECTRIC_IMAGE=electricsql/electric@sha256:ac68b8a4adbf6ea14db8ba0027ec6bc28264887b51e293914d9f94b0cd0bec09 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const { dockerCompose } = require('../util/util.js') | ||
const process = require('process') | ||
|
||
const cliArguments = process.argv.slice(2) | ||
|
||
dockerCompose('up', cliArguments, (code) => { | ||
if (code !== 0) { | ||
console.error( | ||
'\x1b[31m', | ||
'Failed to start the Electric backend. Check the output from `docker compose` above.\n' + | ||
'If the error message mentions a port already being allocated or address being already in use,\n' + | ||
'execute `yarn ports:configure` to run Electric on another port.', | ||
'\x1b[0m' | ||
) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,81 @@ | ||
const shell = require('shelljs') | ||
|
||
let db = process.env.DATABASE_URL | ||
let electricPort = process.env.ELECTRIC_PORT ?? 5133 | ||
let electricProxyPort = process.env.ELECTRIC_PROXY_PORT ?? 65432 | ||
|
||
if (process.argv.length === 4) { | ||
const command = process.argv[2] | ||
let args = process.argv.slice(2) | ||
|
||
if (command !== '-db') { | ||
console.error(`Unsupported option ${command}. Only '-db' option is supported.`) | ||
while (args.length > 0) { | ||
// There are arguments to parse | ||
const flag = args[0] | ||
const value = args[1] | ||
|
||
process.exit(1) | ||
args = args.slice(2) | ||
|
||
const checkValue = () => { | ||
if (typeof value === 'undefined') { | ||
error(`Missing value for option '${flag}'.`) | ||
} | ||
} | ||
|
||
db = process.argv[3] | ||
switch (flag) { | ||
case '-db': | ||
checkValue() | ||
db = value | ||
break | ||
case '--electric-port': | ||
checkValue() | ||
electricPort = parsePort(value) | ||
break | ||
case '--electric-proxy-port': | ||
checkValue() | ||
electricProxyPort = parsePort(value) | ||
break | ||
default: | ||
error(`Unrecognized option: '${flag}'.`) | ||
} | ||
} | ||
else if (process.argv.length !== 2) { | ||
console.log('Wrong number of arguments provided. Only one optional argument `-db <Postgres connection url>` is supported.') | ||
|
||
function parsePort(port) { | ||
// checks that the number is between 0 and 65535 | ||
const portRegex = /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/ | ||
if (!portRegex.test(port)) { | ||
error(`Invalid port '${port}. Port should be between 0 and 65535.'`) | ||
} | ||
return port | ||
} | ||
|
||
if (db === undefined) { | ||
console.error(`Database URL is not provided. Please provide one using the DATABASE_URL environment variable.`) | ||
|
||
process.exit(1) | ||
} | ||
|
||
const electric = process.env.ELECTRIC_IMAGE ?? "electricsql/electric:latest" | ||
|
||
shell.exec('docker pull electricsql/electric:latest') | ||
shell.exec( | ||
// 5433 is the logical publisher port | ||
// it is exposed because PG must be able to connect to Electric | ||
const res = shell.exec( | ||
`docker run \ | ||
-e "DATABASE_URL=${db}" \ | ||
-e "LOGICAL_PUBLISHER_HOST=localhost" \ | ||
-e "AUTH_MODE=insecure" \ | ||
-p 5133:5133 \ | ||
-p ${electricPort}:5133 \ | ||
-p ${electricProxyPort}:65432 \ | ||
-p 5433:5433 ${electric}` | ||
) | ||
|
||
if (res.code !== 0 && res.stderr.includes('port is already allocated')) { | ||
// inform the user that they should change ports | ||
console.error( | ||
'\x1b[31m', | ||
'Could not start Electric because the port seems to be taken.\n' + | ||
'To run Electric on another port execute `yarn ports:configure`', | ||
'\x1b[0m' | ||
) | ||
} | ||
|
||
function error(err) { | ||
console.error('\x1b[31m', err + '\nyarn electric:start [-db <Postgres connection url>] [--electric-port <port>] [--electric-proxy-port <port>]', '\x1b[0m') | ||
process.exit(1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
const { build, serve } = require('esbuild') | ||
|
||
const { createServer, request } = require('http') | ||
const { spawn } = require('child_process') | ||
|
||
const fs = require('fs-extra') | ||
const inlineImage = require('esbuild-plugin-inline-image') | ||
|
||
const shouldMinify = process.env.NODE_ENV === 'production' | ||
const shouldServe = process.env.SERVE === 'true' | ||
|
||
// https://github.com/evanw/esbuild/issues/802#issuecomment-819578182 | ||
const liveServer = (buildOpts) => { | ||
const clients = [] | ||
|
||
build( | ||
{ | ||
...buildOpts, | ||
banner: { js: ' (() => new EventSource("/esbuild").onmessage = () => location.reload())();' }, | ||
watch: { | ||
onRebuild(error, result) { | ||
clients.forEach((res) => res.write('data: update\n\n')) | ||
clients.length = 0 | ||
console.log(error ? error : '...') | ||
}, | ||
} | ||
} | ||
).catch(() => process.exit(1)) | ||
|
||
serve({servedir: 'dist' }, {}) | ||
.then((serveResult) => { | ||
createServer((req, res) => { | ||
const { url, method, headers } = req | ||
|
||
if (url === '/esbuild') | ||
return clients.push( | ||
res.writeHead(200, { | ||
'Content-Type': 'text/event-stream', | ||
'Cache-Control': 'no-cache', | ||
Connection: 'keep-alive', | ||
}) | ||
) | ||
|
||
const path = ~url.split('/').pop().indexOf('.') ? url : `/index.html` //for PWA with router | ||
req.pipe( | ||
request({ hostname: '0.0.0.0', port: serveResult.port, path, method, headers }, (prxRes) => { | ||
res.writeHead(prxRes.statusCode, prxRes.headers) | ||
prxRes.pipe(res, { end: true }) | ||
}), | ||
{ end: true } | ||
) | ||
}).listen(3001) | ||
|
||
setTimeout(() => { | ||
const op = { darwin: ['open'], linux: ['xdg-open'], win32: ['cmd', '/c', 'start'] } | ||
const ptf = process.platform | ||
const url = "http://localhost:3001" | ||
if (clients.length === 0) spawn(op[ptf][0], [...[op[ptf].slice(1)], url]) | ||
console.info(`Your app is running at ${url}`) | ||
}, 500) // open the default browser only if it is not opened yet | ||
}) | ||
} | ||
|
||
/** | ||
* ESBuild Params | ||
* @link https://esbuild.github.io/api/#build-api | ||
*/ | ||
let buildParams = { | ||
color: true, | ||
entryPoints: ["src/index.tsx"], | ||
loader: { ".ts": "tsx" }, | ||
outdir: "dist", | ||
minify: shouldMinify, | ||
format: "cjs", | ||
bundle: true, | ||
sourcemap: true, | ||
logLevel: "error", | ||
incremental: true, | ||
define: { | ||
__DEBUG_MODE__: JSON.stringify(process.env.DEBUG_MODE === 'true'), | ||
__ELECTRIC_URL__: JSON.stringify(process.env.ELECTRIC_URL ?? 'ws://localhost:5133'), | ||
}, | ||
external: ["fs", "path"], | ||
plugins: [inlineImage()], | ||
}; | ||
|
||
(async () => { | ||
fs.removeSync("dist"); | ||
fs.copySync("public", "dist"); | ||
|
||
if (shouldServe) { | ||
liveServer(buildParams) | ||
} | ||
else { | ||
await build(buildParams) | ||
|
||
process.exit(0) | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
const portUsed = require('tcp-port-used') | ||
const prompt = require('prompt') | ||
const path = require('path') | ||
const fs = require('fs/promises') | ||
const { findFirstMatchInFile, fetchConfiguredElectricPort, fetchConfiguredElectricProxyPort } = require('./util/util.js') | ||
|
||
// Regex to check that a number is between 0 and 65535 | ||
const portRegex = /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/ | ||
|
||
init() | ||
|
||
// Wrap code in an anonymous async function | ||
// such that we can await | ||
async function init() { | ||
// Find the old ports for Electric, its proxy, and the webserver | ||
// such that we know which ports to replace | ||
const webserverPortRegex = /listen\(([0-9]+)\)/ | ||
|
||
const packageJsonFile = path.join(__dirname, 'package.json') | ||
const builderFile = path.join(__dirname, 'builder.js') | ||
|
||
const oldElectricPort = await fetchConfiguredElectricPort() | ||
const oldElectricProxyPort = await fetchConfiguredElectricProxyPort() | ||
const oldWebserverPort = await findFirstMatchInFile(webserverPortRegex, builderFile, 'Could not find current webserver port in builder.js') | ||
|
||
prompt.start() | ||
let electricPort = oldElectricPort | ||
let electricProxyPort = oldElectricProxyPort | ||
let webserverPort = oldWebserverPort | ||
|
||
const userInput = await prompt.get({ | ||
properties: { | ||
electricPort: { | ||
description: 'Choose a port for Electric', | ||
type: 'number', | ||
pattern: portRegex, | ||
message: 'Port should be between 0 and 65535.', | ||
default: oldElectricPort | ||
}, | ||
electricProxyPort: { | ||
description: "Choose a port for Electric's DB proxy", | ||
type: 'number', | ||
pattern: portRegex, | ||
message: 'Port should be between 0 and 65535.', | ||
default: electricProxyPort | ||
}, | ||
webserverPort: { | ||
description: 'Choose a port the webserver', | ||
type: 'number', | ||
pattern: portRegex, | ||
message: 'Port should be between 0 and 65535.', | ||
default: oldWebserverPort | ||
}, | ||
} | ||
}) | ||
|
||
electricPort = await checkPort(userInput.electricPort, 'Electric', 5133) | ||
electricProxyPort = await checkPort(userInput.electricProxyPort, "Electric's proxy", 65432) | ||
webserverPort = await checkPort(userInput.webserverPort, 'the web server', 3001) | ||
|
||
// Update port in package.json file | ||
await findAndReplaceInFile(`http://localhost:${oldElectricPort}`, `http://localhost:${electricPort}`, packageJsonFile) | ||
await findAndReplaceInFile( | ||
`postgresql://prisma:proxy_password@localhost:${oldElectricProxyPort}/electric`, | ||
`postgresql://prisma:proxy_password@localhost:${electricProxyPort}/electric`, packageJsonFile | ||
) | ||
|
||
// Update the port on which Electric runs in the builder.js file | ||
await findAndReplaceInFile(`ws://localhost:${oldElectricPort}`, `ws://localhost:${electricPort}`, builderFile) | ||
|
||
// Update the port on which Electric and its proxy run in startElectric.js file | ||
const startElectricFile = path.join(__dirname, 'backend', 'startElectric.js') | ||
await findAndReplaceInFile(oldElectricPort, `${electricPort}`, startElectricFile) | ||
await findAndReplaceInFile(oldElectricProxyPort, `${electricProxyPort}`, startElectricFile) | ||
|
||
// Update the port of the web server of the example in the builder.js file | ||
await findAndReplaceInFile(`listen(${oldWebserverPort})`, `listen(${webserverPort})`, builderFile) | ||
await findAndReplaceInFile(`http://localhost:${oldWebserverPort}`, `http://localhost:${webserverPort}`, builderFile) | ||
|
||
// Update the port for Electric and its proxy in .envrc | ||
const envrcFile = path.join(__dirname, 'backend', 'compose', '.envrc') | ||
await findAndReplaceInFile(`export ELECTRIC_PORT=${oldElectricPort}`, `export ELECTRIC_PORT=${electricPort}`, envrcFile) | ||
await findAndReplaceInFile(`export ELECTRIC_PROXY_PORT=${oldElectricProxyPort}`, `export ELECTRIC_PROXY_PORT=${electricProxyPort}`, envrcFile) | ||
|
||
console.info(`⚡️ Success! Your project is now configured to run Electric on port ${electricPort}, the DB proxy on port ${electricProxyPort}, and the webserver on port ${webserverPort}.`) | ||
} | ||
|
||
/* | ||
* Replaces the first occurence of `find` by `replace` in the file `file`. | ||
* If `find` is a regular expression that sets the `g` flag, then it replaces all occurences. | ||
*/ | ||
async function findAndReplaceInFile(find, replace, file) { | ||
const content = await fs.readFile(file, 'utf8') | ||
const replacedContent = content.replace(find, replace) | ||
await fs.writeFile(file, replacedContent) | ||
} | ||
|
||
/** | ||
* Checks if the given port is open. | ||
* If not, it will ask the user if | ||
* they want to choose another port. | ||
* @returns The chosen port. | ||
*/ | ||
async function checkPort(port, process, defaultPort) { | ||
const portOccupied = await portUsed.check(port) | ||
if (!portOccupied) { | ||
return port | ||
} | ||
|
||
// Warn the user that the chosen port is occupied | ||
console.warn(`Port ${port} for ${process} is already in use.`) | ||
// Propose user to change port | ||
prompt.start() | ||
|
||
const { port: newPort } = (await prompt.get({ | ||
properties: { | ||
port: { | ||
description: 'Hit Enter to keep it or enter a different port number', | ||
type: 'number', | ||
pattern: portRegex, | ||
message: 'Please choose a port between 0 and 65535', | ||
default: port, | ||
} | ||
} | ||
})) | ||
|
||
if (newPort === port) { | ||
// user chose not to change port | ||
return newPort | ||
} | ||
else { | ||
// user changed port, check that it is free | ||
return checkPort(newPort, process, defaultPort) | ||
} | ||
} |
Oops, something went wrong.