Skip to content

Conversation

@GrantASL19
Copy link
Collaborator

Note: I’ve copied this as-is from Jigsaw-Code/outline-sdk#473 but currently the build script fails due to unrelated issues (will investigate further and open an issue/PR).


(Let me know if this is something you’re interested in at all -- I like having type safety for the project but I know TypeScript imposes some additional burden on contributors and maintainers.)

I started this because while working on Jigsaw-Code#472 I noticed getYAMLFileConfig had some additional edge cases where undefined could get returned, but figured I could fully implement type safety in wrapper_app_project while I was at it.

Some notes:

  • Adds a basic tsconfig.json with "strict" enabled, but type checking in basic_navigator_example disabled for now. You can run type checking with a new "typescript:check" script.

  • Adds .vscode/settings.json to configure VS Code to do type checking and match the existing code style

  • I’m not super familiar with the config format so I may be making some incorrect assumptions here.

(Let me know if this is something you’re interested in at all -- I like
having type safety for the project but I know TypeScript imposes some
additional burden on contributors and maintainers.)

I started this because while working on Jigsaw-Code#472 I noticed
getYAMLFileConfig had some additional edge cases where undefined could
get returned, but figured I could fully implement type safety in
wrapper_app_project while I was at it.

Some notes:

* Adds a basic tsconfig.json with "strict" enabled, but type checking in
basic_navigator_example disabled for now. You can run type checking
with a new "typescript:check" script.

* Adds .vscode/settings.json to configure VS Code to do type checking
and match the existing code style

* I’m not super familiar with the config format so I may be making some
incorrect assumptions here.
@GrantASL19 GrantASL19 requested a review from daniellacosse July 11, 2025 19:49
@GrantASL19 GrantASL19 self-assigned this Jul 11, 2025
@GrantASL19 GrantASL19 added the enhancement New feature or request label Jul 11, 2025
* Move Config type to config.mjs

* Move resolveConfiguration to config.mjs, rename to
validateAndNormalizeConfig, change argument names for clarity.

* Move zip function to zip.mjs, rename to compress, change argument
names for clarity.
…ring from resolvedConfig.entryDomain if necessary)
Copy link
Contributor

@fortuna fortuna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but we need to figure out the vscode stuff

…fig) types; separate Config validation (validateConfig) from InternalConfig generation (makeInternalConfig)
*
* @returns {ValidConfig | { error: string }}
*/
export function validateConfig(args) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"config" must be qualified. What is it is that you are configuring?

It looks like the app, so perhaps AppConfig?

But Capacitor already provides an app config, so why do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is this a build config, or a runtime config? Those are different things.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a build config, but the thing being built is the Capacitor project (not the Capacitor app itself).

So technically there are 3 configs:

Maybe something like MakeConfig would make sense, but only if we renamed the build script to make. And the user could confuse this for the Unix make command?

Copy link
Collaborator

@daniellacosse daniellacosse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I would like for us to either use the GTS cli directly or parts of it. We were discussing this to an extent in the CI pr (#12). Try to use those configurations where you can.

@daniellacosse
Copy link
Collaborator

BTW @GrantASL19 - re-requesting the review at the top lets @fortuna and I know that you're ready for another look.

Copy link
Collaborator

@daniellacosse daniellacosse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would, at the least, extend the official https://github.com/google/gts/blob/main/tsconfig-google.json

Also, let me know if you need any help with @fortuna's comments.

Comment on lines 117 to 121
await promisify(exec)(`
cd ${APP_TARGET_DIR.replaceAll(/\s+/g, '\\ ')}
npm install --no-warnings
npx cap sync ${internalConfig.platform}
`);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
@GrantASL19
Copy link
Collaborator Author

@daniellacosse Added GTS in 32519eb. tsconfig.json, .eslintrc.json, and .prettierrc.js now extend it. There are a few config tweaks to support top-level await, import.meta, and .mjs files.

At this point I could convert the .mjs files to TypeScript and run the build with tsx if you’re fine with that (based on your note at the bottom of #12)

daniellacosse
daniellacosse previously approved these changes Jul 23, 2025
Copy link
Collaborator

@daniellacosse daniellacosse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding GTS, I can hook that up in #12 one we merge this! We can rewrite to TS later if needed, prefer to not have to preprocess the scripts if possible.

Copy link
Contributor

@fortuna fortuna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple config formats is a recipe for headache. Prefer to have a single format here with the derivations dynamically computed and no redundancy.

…nd returns config. Note:

1. I added config file validation using a combination of
ts-json-schema-generator (to generate a JSON schema from the ConfigFile
type) and ajv (to validate that the config file matches it). This way
we don’t need to include a bunch of custom validation logic for each
config property.

One trade-off here is that SmartDialerConfig isn’t based on the
underlying Go code, so it could fall out of sync.

If we convert all the scripts to TypeScript we can replace this with
e.g. zod or ts-pattern (which I’m familiar with) by starting with a
pattern and deriving both a type and validator function, rather than
starting with the type and deriving a validator function.

2. To simplify things I made a decision to only accept two CLI
arguments: config (for passing in custom config locations) and platform
(to choose build platform without creating redundant platform-specific
configs). This sidesteps the issues with e.g. passing in
additionalDomains (do we accept a comma-separated list?) and
smartDialerConfig (do we accept a JSON string then parse it?).

3. There are still two types: Config and ConfigFile. ConfigFile (used
for config file validation) is based on Config, but with derived
properties (entryDomain, domainList, and smartDialerConfigBase64)
removed, and the remaining properties other than entryUrl optional.

I don’t see any way to avoid this unless we require that the config file
contains all config properties (including the derived properties).

Also note that I renamed these from "BuildConfig*" to "Config*". I think
"Config" conveys "app maker config" given the name of the project, and
having "build" or "make" in the type name might be more confusing since
both could be read as a verb.

4. I tried replacing the derived properties with getters as suggested in
[1], but to be compatible with the Handlebars template compilation [2]
I think we would need to add a custom toJSON method to the class for
the derived properties, which was making the class increasingly harder
to read and understand vs. just outputting a config object including
the derived properties as I did. IMO this is an acceptable trade-off
given the config object isn’t really a user-facing thing, but let me
know if there’s a better way here.

[1]: #8 (comment)
[2]: https://github.com/Jigsaw-Code/outline-app-maker/blob/7fa63ea3539a105e66d8ee43791166ed21c5bef9/wrapper_app_project/scripts/build.mjs#L107-L114
Copy link
Contributor

@fortuna fortuna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my comment on the types of config

@@ -1,6 +1,6 @@
package {{appId}}

object Config {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
object Config {
object RuntimeConfig {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to rename the file and update references

@@ -1,8 +1,8 @@
import Foundation

struct Config {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
struct Config {
struct RuntimeConfig {

Comment on lines 6 to 7
static var proxyHost: String = "127.0.0.1"
static var proxyPort: String = "0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need these in the config. The ios and android configs should probably be the same.

Suggested change
static var proxyHost: String = "127.0.0.1"
static var proxyPort: String = "0"

);
await fs.writeFile(
destinationFilepath.replace(/\.handlebars$/, ''),
template(config),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define a dedicated type TemplateInput for the template input.
It should be derived from the AppMakerConfig with a clear method, like createTemplateInput(appMakerConfig).

Suggested change
template(config),
template(createTemplateInput(appMakerConfig)),


export type Platform = 'android' | 'ios';

export type Config = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's replace this with an AppMakerConfig that is entirely defined by the user. It should be the file format. Remove ConfigFile.

Comment on lines +96 to +100
await promisify(exec)(`
cd ${APP_TARGET_DIR.replaceAll(/\s+/g, '\\ ')}
npm install --no-warnings
npx cap sync ${config.platform}
`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to properly quote in case of spaces:

Suggested change
await promisify(exec)(`
cd ${APP_TARGET_DIR.replaceAll(/\s+/g, '\\ ')}
npm install --no-warnings
npx cap sync ${config.platform}
`);
await promisify(exec)(`
cd "${APP_TARGET_DIR.replaceAll(/\s+/g, '\\ ')}"
npm install --no-warnings
npx cap sync "${config.platform}"
`);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not safe: npx cap sync ${config.platform}. You need to shell-escape the parameter, or test that they are in a safe set of values.

The safe way to run commands is to use the exec version that takes the args in a list..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants