Skip to content

Commit

Permalink
Add library names to CLI options (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva authored Jul 29, 2024
1 parent e54bdab commit f360459
Show file tree
Hide file tree
Showing 15 changed files with 421 additions and 355 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-olives-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-solana-program": patch
---

Add library names to CLI options
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ pnpm create solana-program --solana 1.18

# Force the creation of the program repository even if the directory is not empty.
pnpm create solana-program --force

# Override the library names.
pnpm create solana-program --program-crate-name acme-counter
pnpm create solana-program --rust-client-crate-name acme-counter-client
pnpm create solana-program --js-client-package-name @acme/counter
```

## Existing Anchor programs
Expand Down
10 changes: 5 additions & 5 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import * as fs from 'node:fs';
import * as path from 'node:path';

import { createOrEmptyTargetDirectory } from './utils/filesystem';
import { getInputs } from './utils/inputs';
import { getInputs } from './utils/inputAll';
import { getLanguage } from './utils/localization';
import { logBanner, logDone, logStep } from './utils/logs';
import { RenderContext, getRenderContext } from './utils/renderContext';
import { renderTemplate } from './utils/renderTemplates';
import { generateKeypair, patchSolanaDependencies } from './utils/solanaCli';
import { detectAnchorVersion } from './utils/version-anchor';
import { detectRustVersion } from './utils/version-rust';
import { detectSolanaVersion } from './utils/version-solana';
import { Version } from './utils/version-core';
import { detectAnchorVersion } from './utils/versionAnchor';
import { detectRustVersion } from './utils/versionRust';
import { detectSolanaVersion } from './utils/versionSolana';
import { Version } from './utils/versionCore';

(async function init() {
logBanner();
Expand Down
2 changes: 1 addition & 1 deletion template/base/Cargo.toml.njk
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ members = ["program"]
{% if programFramework === 'anchor' %}
[profile.release]
overflow-checks = true
{% endif %}

{% endif %}
[workspace.metadata.cli]
{% if programFramework === 'anchor' %}
anchor = "{{ anchorVersion.full }}"
Expand Down
15 changes: 15 additions & 0 deletions utils/inputAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getInputsFromArgs } from './inputArgs';
import { getDefaultInputs, type Inputs } from './inputCore';
import { getInputsFromPrompts } from './inputPrompts';
import type { Language } from './localization';

export async function getInputs(language: Language): Promise<Inputs> {
const argInputs = getInputsFromArgs();
const defaultInputs = getDefaultInputs(argInputs);

if (argInputs.useDefaults) {
return defaultInputs;
}

return getInputsFromPrompts(language, argInputs);
}
101 changes: 101 additions & 0 deletions utils/inputArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { parseArgs } from 'node:util';

import { type Inputs } from './inputCore';
import { kebabCase } from './strings';

type ArgInputs = {
address?: string;
anchorProgram: boolean;
clients: Array<'js' | 'rust'>;
force: boolean;
jsClientPackageName?: string;
noClients: boolean;
organizationName?: string;
programCrateName?: string;
programName?: string;
rustClientCrateName?: string;
rustVersion?: string;
shankProgram: boolean;
solanaVersion?: string;
useDefaults: boolean;
targetDirectoryName?: string;
};

export function getInputsFromArgs(): Partial<Inputs> {
const args = process.argv.slice(2);
const { values: options, positionals } = parseArgs({
args,
options: {
address: { type: 'string' },
anchor: { type: 'boolean' },
client: { type: 'string', multiple: true },
default: { type: 'boolean', short: 'd' },
force: { type: 'boolean' },
'js-client-package-name': { type: 'string' },
'no-clients': { type: 'boolean' },
org: { type: 'string' },
'program-crate-name': { type: 'string' },
rust: { type: 'string' },
'rust-client-crate-name': { type: 'string' },
shank: { type: 'boolean' },
solana: { type: 'string' },
},
strict: false,
});

return parseArgInputs({
address: options.address,
anchorProgram: options.anchor ?? false,
clients: options.client,
force: options.force ?? false,
jsClientPackageName: options['js-client-package-name'],
noClients: options['no-clients'] ?? false,
organizationName: options.org,
programCrateName: options['program-crate-name'],
programName: positionals[1],
rustClientCrateName: options['rust-client-crate-name'],
rustVersion: options.rust,
shankProgram: options.shank ?? false,
solanaVersion: options.solana,
useDefaults: options.default ?? false,
targetDirectoryName: positionals[0],
} as ArgInputs);
}

function parseArgInputs(argInputs: ArgInputs): Partial<Inputs> {
const inputs = {} as Partial<Inputs>;

if (argInputs.address) inputs.programAddress = argInputs.address;
if (argInputs.organizationName)
inputs.organizationName = kebabCase(argInputs.organizationName);
if (argInputs.programName)
inputs.programName = kebabCase(argInputs.programName);
if (argInputs.rustVersion) inputs.rustVersion = argInputs.rustVersion;
if (argInputs.solanaVersion) inputs.solanaVersion = argInputs.solanaVersion;
if (argInputs.targetDirectoryName)
inputs.targetDirectoryName = argInputs.targetDirectoryName;
if (argInputs.jsClientPackageName)
inputs.jsClientPackageName = argInputs.jsClientPackageName;
if (argInputs.programCrateName)
inputs.programCrateName = argInputs.programCrateName;
if (argInputs.rustClientCrateName)
inputs.rustClientCrateName = argInputs.rustClientCrateName;
if (argInputs.force) inputs.shouldOverride = true;
if (argInputs.useDefaults) inputs.useDefaults = true;

if (argInputs.anchorProgram) {
inputs.programFramework = 'anchor';
} else if (argInputs.shankProgram) {
inputs.programFramework = 'shank';
}

if (argInputs.noClients) {
inputs.jsClient = false;
inputs.rustClient = false;
} else if (argInputs.clients) {
inputs.jsClient = argInputs.clients.includes('js');
inputs.rustClient = argInputs.clients.includes('rust');
}

return inputs;
}
80 changes: 80 additions & 0 deletions utils/inputCore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as fs from 'node:fs';

import { Language } from './localization';
import { kebabCase } from './strings';

export const allClients = ['js', 'rust'] as const;
export type Client = (typeof allClients)[number];

export type Inputs = {
jsClient: boolean;
jsClientPackageName: string;
organizationName: string;
programAddress?: string;
programCrateName: string;
programFramework: 'shank' | 'anchor';
programName: string;
rustClient: boolean;
rustClientCrateName: string;
rustVersion?: string;
shouldOverride: boolean;
solanaVersion?: string;
targetDirectoryName: string;
useDefaults: boolean;
};

// export async function getInputs(language: Language): Promise<Inputs> {
// const argInputs = getInputsFromArgs();
// const defaultInputs = getDefaultInputs(argInputs);

// if (argInputs.useDefaults) {
// return defaultInputs;
// }

// return getInputsFromPrompts(language, argInputs);
// }

export function getDefaultInputs(partialInputs: Partial<Inputs>): Inputs {
const organizationName = kebabCase(
partialInputs.organizationName ?? 'solana-program'
);
const parsedTargetDirectoryName = partialInputs.targetDirectoryName
? partialInputs.targetDirectoryName.split('/').pop()
: '';
const programName = kebabCase(
partialInputs.programName ?? (parsedTargetDirectoryName || 'my-program')
);
const programCrateName =
partialInputs.programCrateName ?? `${organizationName}-${programName}`;

return {
jsClient: true,
jsClientPackageName: `@${organizationName}/${programName}`,
organizationName,
programCrateName,
programFramework: 'shank',
programName,
rustClient: true,
rustClientCrateName: `${programCrateName}-client`,
shouldOverride: false,
targetDirectoryName: programName,
useDefaults: false,
...partialInputs,
};
}

function canSkipEmptying(dir: fs.PathLike) {
if (!fs.existsSync(dir)) {
return true;
}

const files = fs.readdirSync(dir);
if (files.length === 0) {
return true;
}
if (files.length === 1 && files[0] === '.git') {
return true;
}

return false;
}
Loading

0 comments on commit f360459

Please sign in to comment.