Skip to content

Commit

Permalink
Fix REPL tests in CI
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate committed Dec 19, 2024
1 parent fd676c8 commit b066407
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-items-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nx.js/patch-nacp": major
---

Support parsers and formatters
130 changes: 109 additions & 21 deletions packages/patch-nacp/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,103 @@
import parseAuthor from 'parse-author';
import { readFileSync } from 'node:fs';
import { NACP } from '@tootallnate/nacp';
import { NACP, VideoCapture } from '@tootallnate/nacp';
import { titleCase } from 'title-case';
import type { PackageJson as BasePackageJson } from 'types-package-json';

export type PackageJsonNacp = Omit<NACP, 'buffer'>;

// TODO: move this to `@tootallnate/nacp`
enum Screenshot {
Enabled,
Disabled,
}

export interface PackageJson extends BasePackageJson {
/**
* @deprecated Use `nacp.id` instead.
*/
titleId?: string;
/**
* @deprecated Use `nacp.title` instead.
*/
productName?: string;
/**
* Additional NACP properties to set.
*/
nacp?: PackageJsonNacp;
}

export type NacpProperty = Exclude<keyof NACP, 'buffer'>;

const VALID_NACP_PROPERTIES = Object.getOwnPropertyNames(NACP.prototype);

const parseBigInt = (k: string, v: unknown): bigint => {
if (typeof v === 'number') return BigInt(v);
if (typeof v === 'string') return BigInt(v);
throw new Error(`Invalid BigInt value for "${k}": ${v}`);
};

const parseTitleId = (k: string, v: unknown): bigint => {
if (typeof v === 'string') {
if (v.length !== 16) {
throw new Error(`"${k}" must be 16 hexadecimal digits`);
}
return BigInt(`0x${v}`);
}
throw new Error(`Invalid Title ID value for "${k}": ${v}`);
};

const parseEnum =
<
T extends {
[K in keyof T]: K extends string ? number : string;
},
>(
Enum: T,
trueValue?: keyof T,
falseValue?: keyof T,
) =>
(k: string, v: unknown): number => {
let val: number | undefined;
if (typeof v === 'string') {
val = Enum[v as keyof T] as number;
} else if (typeof v === 'boolean') {
val = Enum[v ? trueValue : falseValue] as number;
} else if (typeof v === 'number') {
val = v;
}
if (
typeof val === 'undefined' ||
typeof Enum[val as keyof T] !== 'string'
) {
throw new Error(`Invalid "${k}" value: ${v}`);
}
return val;
};

const parsers: {
[T in NacpProperty]?: (k: T, v: unknown) => NACP[T];
} = {
id: parseTitleId,
saveDataOwnerId: parseTitleId,
userAccountSaveDataSize: parseBigInt,
videoCapture: parseEnum(VideoCapture, 'Enabled', 'Disabled'),
screenshot: parseEnum(Screenshot, 'Enabled', 'Disabled'),
};

const formatTitleId = (v: bigint) => v.toString(16).padStart(16, '0');

const formatters: {
[T in NacpProperty]?: (v: NACP[T]) => string;
} = {
id: formatTitleId,
saveDataOwnerId: formatTitleId,
userAccountSaveDataSize: (v) =>
v === 0n ? `${v} (\`localStorage\` disabled)` : String(v),
videoCapture: (v) => VideoCapture[v],
screenshot: (v) => Screenshot[v],
};

export function patchNACP(nacp: NACP, packageJsonUrl: URL) {
const warnings: string[] = [];
const updated = new Map<string, string>();
Expand All @@ -33,7 +117,7 @@ export function patchNACP(nacp: NACP, packageJsonUrl: URL) {
'The "titleId" property is deprecated. Use "nacp.id" instead.',
);
nacp.id = titleId;
updated.set('ID', titleId);
updated.set('ID', getFormatter('id')(nacp.id));
}

const title = productName || name;
Expand All @@ -43,20 +127,20 @@ export function patchNACP(nacp: NACP, packageJsonUrl: URL) {
'The "productName" property is deprecated. Use "nacp.title" instead.',
);
}
updated.set('Title', title);
nacp.title = title;
updated.set('Title', getFormatter('title')(nacp.title));
}

if (version) {
nacp.version = version;
updated.set('Version', version);
updated.set('Version', getFormatter('version')(nacp.version));
}

const author =
typeof rawAuthor === 'string' ? parseAuthor(rawAuthor) : rawAuthor;
if (author?.name) {
nacp.author = author.name;
updated.set('Author', author.name);
updated.set('Author', getFormatter('author')(nacp.author));
}

for (const [k, v] of Object.entries(pkgNacp)) {
Expand All @@ -65,23 +149,27 @@ export function patchNACP(nacp: NACP, packageJsonUrl: URL) {
continue;
}

// @ts-expect-error
const oldValue = nacp[k];
// @ts-expect-error
nacp[k] = v;
// @ts-expect-error
const newValue = nacp[k];

if (newValue !== oldValue) {
const titleCased =
k === 'id' ? 'ID' : titleCase(k.replace(/([A-Z])/g, ' $1'));
let label = newValue;
if (k === 'userAccountSaveDataSize' && newValue === 0n) {
label = `${newValue} (\`localStorage\` disabled)`;
}
updated.set(titleCased, label);
}
const newValue = updateValue(nacp, k as NacpProperty, v);

const titleCased =
k === 'id' ? 'ID' : titleCase(k.replace(/([A-Z])/g, ' $1'));
const formatter = getFormatter(k as NacpProperty);
updated.set(titleCased, formatter(newValue));
}

return { packageJson, updated, warnings };
}

function getFormatter<T extends NacpProperty>(key: T): (v: NACP[T]) => string {
return formatters[key] ?? String;
}

function updateValue<T extends NacpProperty>(
nacp: NACP,
key: T,
value: unknown,
): NACP[T] {
const parser = parsers[key] ?? ((_, v): NACP[T] => v as NACP[T]);
nacp[key] = parser(key, value);
return nacp[key];
}
2 changes: 1 addition & 1 deletion packages/repl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"access": "public"
},
"dependencies": {
"kleur": "^4.1.5",
"kleur": "github:TooTallNate/kleur#rgb",
"sisteransi": "^1.0.5"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions packages/repl/test/repl.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { $ } from 'kleur/colors';
$.enabled = true;

import { createInspect } from '@nx.js/inspect';
import { describe, expect, test } from 'vitest';
import { REPL, type REPLOptions } from '../src/index';
Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

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

0 comments on commit b066407

Please sign in to comment.