Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
37c7cdc
Add TypeScript checking via JSDoc type annotations
GrantASL19 Jul 11, 2025
affda93
Remove "js/ts.implicitProjectConfig.checkJs" setting (unnecessary)
GrantASL19 Jul 16, 2025
f42f2fb
Exclude output directory from type checking
GrantASL19 Jul 16, 2025
b56eac0
Improve file organization and documentation:
GrantASL19 Jul 16, 2025
ec43d3b
Use inputConfig.appId as resolvedConfig.appId if provided (only infer…
GrantASL19 Jul 16, 2025
8f15d90
Merge branch 'main' into feature/add-typescript-checking
GrantASL19 Jul 16, 2025
61e63e4
Rename .scripts directories to scripts
GrantASL19 Jul 16, 2025
cc58ab9
In validateAndNormalizeConfig pass through entryUrl (used in capacito…
GrantASL19 Jul 16, 2025
75355ef
Build into provided output, falling back to `output/${new URL(inputCo…
GrantASL19 Jul 16, 2025
d88047e
Fix type error
GrantASL19 Jul 16, 2025
4894831
In tsconfig.json remove hidden directories from include (renamed .scr…
GrantASL19 Jul 16, 2025
f740949
Separate public (Config/ValidConfig) and internal config (InternalCon…
GrantASL19 Jul 16, 2025
808ca62
Rename InternalConfig["smartDialerConfig"] to InternalConfig["smartDi…
GrantASL19 Jul 16, 2025
56d65e5
Add VS Code configs to auto-format with final newlines and remove tra…
GrantASL19 Jul 17, 2025
9e1bdd0
Rename types and variables: Config → RawBuildConfig, ValidConfig → Va…
GrantASL19 Jul 18, 2025
e13a8fc
Refactor validateRawBuildConfig into isValidRawBuildConfig type guard
GrantASL19 Jul 18, 2025
a210e9b
Improve type descriptions
GrantASL19 Jul 18, 2025
42f83cd
Rename .vscode/settings.json to .vscode.suggestion/settings.json; add…
GrantASL19 Jul 18, 2025
a0ba7ce
Validate that rawBuildConfig.entryUrl is a valid URL; add fallback "a…
GrantASL19 Jul 18, 2025
32519eb
Add https://github.com/google/gts (tsconfig.json, .eslintrc.json, and…
GrantASL19 Jul 23, 2025
f15f821
In resolveBuildConfig replace nested ternary with if...else statements
GrantASL19 Jul 24, 2025
a2059f0
Inline DEFAULT_CONFIG into rawBuildConfig
GrantASL19 Jul 24, 2025
7fa63ea
Rename getYAMLBuildConfig → yamlBuildConfigToObject
GrantASL19 Jul 24, 2025
ec8e4f1
Moved all configuration logic into createConfig.mjs, which validate a…
GrantASL19 Jul 25, 2025
0265c46
Replace SmartDialerConfig type with object
GrantASL19 Jul 30, 2025
303a6e0
Add copyright headers
GrantASL19 Jul 30, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
output/
node_modules/
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.importModuleSpecifierEnding": "auto",
"typescript.preferences.quoteStyle": "single",
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
}
76 changes: 57 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "outline-app-maker",
"dependencies": {
"@ngrok/ngrok": "1.4.1",
"archiver": "^7.0.1",
Expand All @@ -9,9 +10,12 @@
"yaml": "^2.7.1"
},
"devDependencies": {
"@types/archiver": "^6.0.3",
"@types/minimist": "^1.2.5",
"glob": "^11.0.1",
"handlebars": "^4.7.8",
"vite": "^6.2.7"
"typescript": "^5.8.3",
"vite": "^6.2.0"
},
"license": "Apache-2.0",
"scripts": {
Expand All @@ -20,7 +24,8 @@
"clean": "npx rimraf node_modules/ output/",
"doctor": "./doctor",
"reset": "npm run clean && npm ci",
"start:navigator": "node ./basic_navigator_example/.scripts/start.mjs"
"start:navigator": "node ./basic_navigator_example/.scripts/start.mjs",
"typescript:check": "tsc --noEmit"
},
"private": true
}
25 changes: 25 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"module": "node16",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
"include": [
"**/*.cjs",
"**/*.mjs",
"**/*.js",
"**/.*/**/*.cjs",
"**/.*/**/*.mjs",
"**/.*/**/*.js",
],
"exclude": [
"basic_navigator_example", // TODO: Remove this exclusion?
"node_modules",
"output"
]
}
8 changes: 4 additions & 4 deletions wrapper_app_project/.scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import chalk from 'chalk'
import { glob } from 'glob'
import handlebars from 'handlebars'

import { getCliConfig, getYAMLFileConfig, DEFAULT_CONFIG } from './config.mjs'
import { resolveConfiguration, zip } from './util.mjs'
import { getCliConfig, getYAMLFileConfig, DEFAULT_CONFIG, validateAndNormalizeConfig } from './config.mjs'
import { compress } from './zip.mjs'

const TEMPLATE_DIR = path.join(process.cwd(), 'wrapper_app_project/template');

Expand All @@ -32,7 +32,7 @@ if (import.meta.url !== pathToFileURL(`${process.argv[1]}`).href) {
throw new Error('Build script must be run from the cli')
}

const config = resolveConfiguration({
const config = validateAndNormalizeConfig({
...DEFAULT_CONFIG,
...(await getYAMLFileConfig('config.yaml')),
...getCliConfig(process.argv)
Expand Down Expand Up @@ -88,7 +88,7 @@ await promisify(exec)(`
`)

console.log(chalk.green(`Zipping project to ${chalk.blue(APP_TARGET_ZIP)}...`))
await zip(APP_TARGET_DIR, APP_TARGET_ZIP)
await compress(APP_TARGET_DIR, APP_TARGET_ZIP)

console.log(chalk.bgGreen('Project ready!'))

Expand Down
102 changes: 97 additions & 5 deletions wrapper_app_project/.scripts/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ import minimist from 'minimist'
import YAML from 'yaml'


/**
* @typedef {{
* additionalDomains: Array<string>;
* appId: string;
* appName: string;
* domainList: string;
* entryDomain: string;
* output: string;
* platform: string;
* smartDialerConfig: string;
* }} Config
*/

export const DEFAULT_CONFIG = {
output: path.join(process.cwd(), 'output'),
smartDialerConfig: JSON.stringify({
Expand All @@ -22,24 +35,103 @@ export const DEFAULT_CONFIG = {
})
}

/**
* Parse a provided YAML file, returning an object.
*
* If the YAML file doesn't parse as an object (e.g. if it contains a primitive
* or sequence/list) an empty object will be returned instead (and a warning
* will be output).
*
* @param {string} filepath
* @returns {Promise<{}>}
*/
export async function getYAMLFileConfig(filepath) {
try {
const data = await fs.readFile(filepath, 'utf8')

if (data) {
return YAML.parse(data)
const parsedData = YAML.parse(data);

if (parsedData && typeof parsedData === 'object' && !Array.isArray(parsedData)) {
// This type assertion may not be 100% guaranteed but for the purposes
// of this use case should be correct
return /** @type {{}} */ (parsedData);
} else {
console.warn(`${filepath} contained invalid config data:`, parsedData)
}
} else {
console.warn(`${filepath} contained no data`)
}
} catch (e) {
if ('ENOENT' == e.code) {
return {}
}
console.warn(`Error loading ${filepath}:`, e)
}

return {};
}

/**
* @param {NodeJS.Process["argv"]} args
*/
export function getCliConfig(args) {
const dict = minimist(args)
return {
...dict,
additionalDomains: dict.additionalDomains?.split(',') ?? []
}
}
}

/**
* Validate and normalize a provided (unknown) config, throwing an error if
* required parameters are missing.
*
* @param {Record<string, unknown>} inputConfig
* @returns {Config}
*/
export function validateAndNormalizeConfig(inputConfig) {
const resolvedConfig = {};

if (typeof inputConfig.entryUrl !== "string") {
throw new Error(`"entryUrl" parameter not provided in parameters or YAML configuration.`);
}
if (typeof inputConfig.platform !== "string") {
throw new Error(`"platform" parameter not provided in parameters or YAML configuration.`);
}

resolvedConfig.entryDomain = new URL(inputConfig.entryUrl).hostname;
resolvedConfig.output = new URL(inputConfig.entryUrl).hostname;
resolvedConfig.platform = inputConfig.platform;


resolvedConfig.appId = typeof inputConfig.appId === "string"
? inputConfig.appId
// Infer an app ID from the entry domain by reversing it (e.g. `www.example.com` becomes `com.example.www`)
// It must be lower case, and hyphens are not allowed.
: resolvedConfig.entryDomain
.replaceAll('-', '')
.toLocaleLowerCase()
.split('.')
.reverse()
.join('.')

if (!inputConfig.appName) {
// Infer an app name from the base entry domain part by title casing the root domain:
// (e.g. `www.my-example-app.com` becomes "My Example App")
resolvedConfig.appName = resolvedConfig.entryDomain
.split('.')
.reverse()[1]
.split(/[-_]+/)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')
}

resolvedConfig.additionalDomains = (
Array.isArray(inputConfig.additionaldomain) &&
inputConfig.additionaldomain.every((item) => typeof item === "string")
)
? inputConfig.additionaldomain
: []
resolvedConfig.domainList = [resolvedConfig.entryDomain, ...resolvedConfig.additionalDomains].join('\n')
resolvedConfig.smartDialerConfig = Buffer.from(JSON.stringify(inputConfig.smartDialerConfig)).toString('base64')

return resolvedConfig;
}
Loading