miércoles, 11 de enero de 2023

Azure DevOps: Sample Android AAB build and Play Store publish

 # Expected configurations

# Variables:
# - KEYSTORE_PASSWORD
# - KEYSTORE_KEY_ALIAS
# - KEYSTORE_KEY_PASSWORD
# - APPCENTER_SECRET_JSON
# Secure file: my-upload-keystore.keystore

parameters:
- name: nodeVersion
type: string
- name: workingDirectory
type: string

# https://learn.microsoft.com/azure/devops/pipelines/ecosystems/android
steps:
# For analytics and crash reports
- script: |
echo $(APPCENTER_SECRET_JSON) > android/app/src/main/assets/appcenter-config.json
displayName: "AppCenter: Copy appcenter-config.json to Android folder"
workingDirectory: ${{parameters.workingDirectory}}

- task: NodeTool@0
displayName: "Install Node.js version ${{parameters.nodeVersion}}"
inputs:
versionSpec: ${{parameters.nodeVersion}}

- script: |
npm install
displayName: "Install NPM dependencies"
workingDirectory: ${{parameters.workingDirectory}}

- task: Gradle@3
displayName: "Android: Build AAB (gradle)"
inputs:
workingDirectory: ${{parameters.workingDirectory}}/android
gradleWrapperFile: ${{parameters.workingDirectory}}/android/gradlew
gradleOptions: |
-Xmx3072m
-DAZURE_DEVOPS_BUILD_ID=$(Build.BuildId)
publishJUnitResults: false
testResultsFiles: "**/TEST-*.xml"
tasks: "bundleRelease"

- task: AndroidSigning@2
displayName: "Android: Sign and align AAB"
inputs:
apkFiles: ${{parameters.workingDirectory}}/android/app/build/outputs/bundle/release/app-release.aab
jarsign: true
jarsignerKeystoreFile: "my-upload-keystore.keystore"
jarsignerKeystorePassword: $(KEYSTORE_PASSWORD)
jarsignerKeystoreAlias: $(KEYSTORE_KEY_ALIAS)
jarsignerKeyPassword: $(KEYSTORE_KEY_PASSWORD)
jarsignerArguments: "-sigalg SHA256withRSA -digestalg SHA-256"
zipalign: true

- task: GooglePlayRelease@4
displayName: "Google Play release"
inputs:
serviceEndpoint: "Google Play Service Connection"
applicationId: "com.acme.app"
action: "SingleBundle"
bundleFile: ${{parameters.workingDirectory}}/android/app/build/outputs/bundle/release/app-release.aab
track: "internal"

jueves, 17 de noviembre de 2022

React Native run-android notes

 

  • runAndroid:
    • ...logger.info('JS server already running.')...
    • buildAndRun
      • cmd = process.platform.startsWith('win') ? 'gradlew.bat' : './gradlew';
      • runOnAllDevices
        • ...logger.info('Installing the app...');...
        • tryLaunchAppOnDevice
          • ...const {appId, appIdSuffix} = args;...
          • ...shell am start -n...

viernes, 4 de noviembre de 2022

capacitor-community / text-to-speech: "Not yet initialized or not available on this device"

Sending plugin error: {"save":false,"callbackId":"...","pluginId":"TextToSpeech",
"methodName":"speak","success":false,"error":{"message":"Not yet initialized or
not available on this device.","code":"UNAVAILABLE"}}

It happened to me only on the emulator. On a real device, it worked.


miércoles, 1 de junio de 2022

Re: How to bundle and use custom web fonts in SPFx projects (Prod mode)

SPFx: 1.14/1.15.0-rc.0 

I followed How to bundle and use custom web fonts in SPFx projects but didn't work for me in "Prod mode" (gulp bundle --ship && gulp package-solution --ship). It worked for me at "Dev time" (gulp serve in Workbench or in a Sharepoint page in "full trust client-side solution" mode, no --ship).

To make it work I had to change the outputPath value (in fontLoaderConfig) from 'fonts/' to '/'.


const fontLoaderConfig = {
test: /\.(woff(2)?)(\?v=\d+\.\d+\.\d+)?$/, // I'm only checking for woff2
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/'
}
}]
};


I noticed that the runtime font references were like: https://[mySite].sharepoint.com/sites/appcatalog/ClientSideAssets/[GUID]/fonts/[myFont].woff2 but visiting https://[mySite].sharepoint.com/sites/appcatalog/ClientSideAssets/[GUID], all the assets were at the same level, like https://[mySite].sharepoint.com/sites/appcatalog/ClientSideAssets/[GUID]/[myFont].woff2.


jueves, 17 de febrero de 2022

AWS SES MessageRejected 400 Bad Request

 Are you on the SES sandbox and trying to send messages to an email address different than the verified one? Or to a domain different than the verified one? 😛



martes, 5 de octubre de 2021

MODULE_NOT_FOUND using New Relic with Nest (NestJS) 8

When adding the newrelic.js file in the root of the NestJS project, and then building it and running it in prod mode:

> node dist/main

node:internal/modules/cjs/loader:936

  throw err;

  ^

Error: Cannot find module '/.../apps/backend/dist/main'

    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)

    at Function.Module._load (node:internal/modules/cjs/loader:778:27)

    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)

    at node:internal/main/run_main_module:17:47 {

  code: 'MODULE_NOT_FOUND',

  requireStack: []

}

In this case, tsc compiles just the newrelic.js file in your dist folder.


Move the newrelic.js to the same directory as your main.ts file (normally src/).

Complete installation instructions: https://discuss.newrelic.com/t/new-relic-for-nestjs/91812. I didn't follow all those for Nest 8 though. In my case:

  1. Same.
  2. Same.
  3. Same.
  4. Same. It didn't work with import _ from 'newrelic';. In that way, it got stripped from the compiled JS.
  5. 6. 7. I didn't need to. The newrelic.js automatically ended up in the dist/ folder.





jueves, 2 de septiembre de 2021

MongoDB Atlas notes

Notes

  • Remember to create a Project per environment (ref)
    • Every database user in a project has access to all the database deployments in the project (you can still restrict access to specific database names across all the deployments)
    • The VPC peering configuration is done per project, then at the AWS side, any peered VPC will have access to all the database deployments and databases)

martes, 31 de agosto de 2021

Stopped reason ResourceInitializationError: failed to download env files: file download command: non empty error stream: RequestCanceled: request context canceled caused by: context deadline exceeded

Error: Stopped reason ResourceInitializationError: failed to download env files: file download command: non empty error stream: RequestCanceled: request context canceled caused by: context deadline exceeded

Using: CloudFormation, ECS Fargate

For some reason, the public subnets where the ECS Fargate instances were deployed lost the explicit association to a route table that I also had in the same CloufFormation template. I had to comment the related AWS::EC2::SubnetRouteTableAssociation's, update the stack, uncomment them and update the stack again.

querySrv ENODATA checking a Mongo SRV record using Node

The Mongo driver prefix the "hostname" with "_mongodb._tcp.".

Reference https://github.com/mongodb/node-mongodb-native/blob/e69d9925713ede3bd80d7d23a6df60c6dd4542ef/src/connection_string.ts#L78

function getMongoSrvHostnameFromDbConnectionString(dbConnectionString: string) {
const startIndex = dbConnectionString.indexOf('@') + 1;
const endIndex = dbConnectionString.indexOf('/', startIndex);
const dbHostname = dbConnectionString.substring(startIndex, endIndex);
// Ref: https://github.com/mongodb/node-mongodb-native/blob/e69d9925713ede3bd80d7d23a6df60c6dd4542ef/src/connection_string.ts#L78
return `_mongodb._tcp.${dbHostname}`;
}

async function resolveSrvHostnamesOrError(
hostname: string,
): Promise<string[] | string> {
const resolveSrv = util.promisify(dns.resolveSrv);
const lookup = util.promisify(dns.lookup);
let ipsOrError: string[] | string = null;
try {
const resolvedHostnames = await resolveSrv(hostname);
const resolvedAddresses = await Promise.all(
resolvedHostnames.map(
async (resolvedHostname) => await lookup(resolvedHostname.name),
),
);

return await Promise.all(
resolvedAddresses.map(
async (resolvedAddress) => (await resolvedAddress).address,
),
);
} catch (err) {
console.log(err);
const e: Error = err;
ipsOrError = e.message;
return ipsOrError;
}
}

miércoles, 18 de agosto de 2021

CloudFormation: resource "already exists in stack" after updating logical names

After refactoring a CloudFormation to have better logical names, I got a bunch of "already exists in stack" errors. Example:

2021-08-18 15:22:20 UTC+1000 BackendEcsTaskRole CREATE_FAILED x-ecs-executtaskion-role already exists in stack arn:aws:cloudformation:us-east-1:y:stack/x/ff05-11eb-80bd
2021-08-18 15:22:20 UTC+1000 BackendLogGroup CREATE_FAILED /x/backend already exists in stack arn:aws:cloudformation:us-east-1:616020545883:stack/x/ff05-11eb-80bd
2021-08-18 15:22:20 UTC+1000 BackendEcsExecutionRole CREATE_FAILED x-ecs-execution-role already exists in stack arn:aws:cloudformation:us-east-1:x:stack/x/ff05-11eb-80bd
2021-08-18 15:22:20 UTC+1000 BackendEcsCluster CREATE_FAILED x-ecs-cluster already exists in stack arn:aws:cloudformation:us-east-1:x:stack/x/ff05-11eb-80bd

If the resources can be safely deleted, one option is to create a temporary template with those conflicting resources removed. Submit it and have CloudFormation delete the physical resources. Then, submit again the refactored template letting CloudFormation create them again succesfully.


viernes, 9 de julio de 2021

AWS ECS cli calls (update-service, create-service) hang on CI/CD (Buildkite, CircleCI)

The AWS CLI v2 tries to use a client-side pager by default. Then, in a lot of cases, it'll wait for interactive input before returning 🤷🏻‍♂️. You can "use the --no-cli-pager command-line option to disable the pager for a single command use" (or use any of the other options described there).

Some SO questions around this:


jueves, 21 de enero de 2021

Deploying cp100-bookshelf on GCP App Engine (sample Python bookshelf app)

In Google Cloud Shell, using Python 2 (the version I get in app.yml, runtime: python27)

mkdir src

cd src git clone https://github.com/GoogleCloudPlatformTraining/cp100-bookshelf.git

Edit cloud-storage/bookshelf/storage.py line 20, change


from werkzeug import secure_filename


to


from werkzeug.utils import secure_filename


Otherwise, you'll get:


ImportError: cannot import name secure_filename

at

<module> (/base/data/home/apps/.../bookshelf/storage.py:20)


Then:


cd cloud-storage

pip install -t lib -r requirements.txt

gcloud app deploy


cd app-engine

pip install -t lib -r requirements.txt

gcloud app deploy




If you don't pip install requirements, you'll get something like:

ValueError: virtualenv: cannot access lib: No such virtualenv or site directory
at add (/base/alloc/tmpfs/dynamic_runtimes/python27g/.../python27/python27_lib/versions/1/google/appengine/ext/vendor/__init__.py:44)


jueves, 5 de noviembre de 2020

Exporting TS React components handling SVGs in a NPM package to be consumed potentially by CRA

Use the same SVG module definition as CRA: https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/lib/react-app.d.ts#L47


declare module '*.svg' {

  import * as React from 'react';

  export const ReactComponent: React.FunctionComponent<React.SVGProps<

    SVGSVGElement

  > & { title?: string }>;

  const src: string;

  export default src;

}


Use both @svgr/webpack and url-loader together in webpack config:

{
  test: /\.svg$/,
  use: ['@svgr/webpack', 'url-loader'], 
} 



miércoles, 4 de noviembre de 2020

Cannot find module 'assert' (from Doctrine) when adding React Styleguidist with plain Webpack

 Error:

utility.js:32 Uncaught Error: Cannot find module 'assert'
    at webpackMissingModule (utility.js:32)
    at eval (utility.js:32)
    at eval (utility.js:33)
    at Object../node_modules/doctrine/lib/utility.js (main.bundle.js:6860)
    at __webpack_require__ (main.bundle.js:23007)
    at fn (main.bundle.js:23218)
    at eval (typed.js:27)
    at eval (typed.js:1304)
    at Object../node_modules/doctrine/lib/typed.js (main.bundle.js:6844)
    at __webpack_require__ (main.bundle.js:23007)

This comes from Doctrine, which doesn't declares "assert" as a (peer) dependency. I installed the latest version (2.x) and it didn't work either. I compared with another app that had that dependency satisfied transitively from another module and it had "assert":"1.4.1" installed. I manually installed that one and it worked.


miércoles, 13 de mayo de 2020

Shutting up ASP.NET debug logs using Serilog

Log example:

{
"@t": "2020-05-13T23:39:04.3578970Z",
"@mt": "Executed action method {ActionName}, returned result {ActionResult} in {ElapsedMilliseconds}ms.",
// ...
"SourceContext": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker",
//...
}

Serilog configuration:

"Serilog": {
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
"restrictedToMinimumLevel": "Information"
}
}
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.AspNetCore": "Warning",
"System": "Warning"
}
}
},

lunes, 28 de octubre de 2019

Newtonsoft Json.NET + JsonApiSerializer: Path 'data'...type requires a JSON array

Sample code

    class Program
    {
        private static readonly HttpClient client = new HttpClient();

        static void Main(string[] args)
        {
            var cats = ProcessCats().Result;

            foreach (var cat in cats)
            {
                Console.WriteLine(cat);
                Console.WriteLine();
            }
        }

        private static async Task<Cat[]> ProcessCats()
        {
            client.DefaultRequestHeaders.Accept.Clear();
//...

            var stream = await client.GetStreamAsync("https://myapi/cats");

            var serializerSettings = new JsonApiSerializerSettings();
            var serializer = JsonSerializer.CreateDefault(serializerSettings);

            using (var textReader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(textReader))
                return serializer.Deserialize<Cat[]>(jsonReader);

        }
    }

   public class Cat
    {
        public string Name { get; set; }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append("class Cat {\n");
            sb.Append("  Name: ").Append(Name).Append("\n");
            sb.Append("}\n");
            return sb.ToString();
        }
    }


Error

Unhandled Exception: System.AggregateException: One or more errors occurred. (Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Cat[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'data', line 1, position 8.) ---> Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Cat[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'data', line 1, position 8.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize[T](JsonReader reader)
   at WebAPIClient.Program.ProcessCats() in /Users/esteban-work/tmp/delete/samples/csharp/getting-started/console-webapiclient/Program.cs:line 42
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at WebAPIClient.Program.Main(String[] args) in /Users/esteban-work/tmp/delete/samples/csharp/getting-started/console-webapiclient/Program.cs:line 17

Fix

Remember to check if the root element has an Id property.

   public class Cat
    {
        public string Id { get; set; }
        public string Name { get; set; }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append("class ContactForClient {\n");
            sb.Append("  Id: ").Append(Id).Append("\n");
            sb.Append("  Name: ").Append(Name).Append("\n");
            sb.Append("}\n");
            return sb.ToString();
        }
    }


References




miércoles, 23 de enero de 2019

nginx: [emerg] host not found in upstream "nodejs" enabling TLS in AWS EB

Error:

2019-01-23 22:44:55    ERROR   [Instance: i-xx] Command failed on instance. Return code: 1 Output: (TRUNCATED)... /etc/nginx/sites-enabled/elasticbeanstalk-nginx-docker-proxy.conf:11
nginx: [emerg] host not found in upstream "nodejs" in /etc/nginx/conf.d/https.conf:19
nginx: configuration file /etc/nginx/nginx.conf test failed
Failed to start nginx, abort deployment.
Hook /opt/elasticbeanstalk/hooks/appdeploy/enact/01flip.sh failed. For more detail, check /var/log/eb-activity.log using console or EB CLI.

I was using a .ebextensions/singlehttps.config sample file from a Node Web Server Elastic Beanstalk app, with something like this,

location / {
proxy_pass http://nodejs;
proxy_set_header Connection "";
proxy_http_version 1.1;

but no upstream declaration because according AWS doc, "the default nginx configuration forwards traffic to an upstream server named nodejs at 127.0.0.1:8081".

But this App is a docker one (which has a Node app running on port 3500), not a nodejs one.

Looking at the EB instance's /etc/nginx/conf.d dir, this file declares a default upstream for docker:

elasticbeanstalk-nginx-docker-upstream.conf:

upstream docker {
    server xx.xx.x.x:yyy;
    keepalive 256;
}

Then,


location / {
proxy_pass http://docker;
proxy_set_header Connection "";
proxy_http_version 1.1;