Skip to content

Commit f360459

Browse files
authored
Add library names to CLI options (#78)
1 parent e54bdab commit f360459

15 files changed

+421
-355
lines changed

.changeset/curly-olives-lie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-solana-program": patch
3+
---
4+
5+
Add library names to CLI options

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ pnpm create solana-program --solana 1.18
6767

6868
# Force the creation of the program repository even if the directory is not empty.
6969
pnpm create solana-program --force
70+
71+
# Override the library names.
72+
pnpm create solana-program --program-crate-name acme-counter
73+
pnpm create solana-program --rust-client-crate-name acme-counter-client
74+
pnpm create solana-program --js-client-package-name @acme/counter
7075
```
7176

7277
## Existing Anchor programs

index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import * as fs from 'node:fs';
44
import * as path from 'node:path';
55

66
import { createOrEmptyTargetDirectory } from './utils/filesystem';
7-
import { getInputs } from './utils/inputs';
7+
import { getInputs } from './utils/inputAll';
88
import { getLanguage } from './utils/localization';
99
import { logBanner, logDone, logStep } from './utils/logs';
1010
import { RenderContext, getRenderContext } from './utils/renderContext';
1111
import { renderTemplate } from './utils/renderTemplates';
1212
import { generateKeypair, patchSolanaDependencies } from './utils/solanaCli';
13-
import { detectAnchorVersion } from './utils/version-anchor';
14-
import { detectRustVersion } from './utils/version-rust';
15-
import { detectSolanaVersion } from './utils/version-solana';
16-
import { Version } from './utils/version-core';
13+
import { detectAnchorVersion } from './utils/versionAnchor';
14+
import { detectRustVersion } from './utils/versionRust';
15+
import { detectSolanaVersion } from './utils/versionSolana';
16+
import { Version } from './utils/versionCore';
1717

1818
(async function init() {
1919
logBanner();

template/base/Cargo.toml.njk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ members = ["program"]
99
{% if programFramework === 'anchor' %}
1010
[profile.release]
1111
overflow-checks = true
12-
{% endif %}
1312

13+
{% endif %}
1414
[workspace.metadata.cli]
1515
{% if programFramework === 'anchor' %}
1616
anchor = "{{ anchorVersion.full }}"

utils/inputAll.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getInputsFromArgs } from './inputArgs';
2+
import { getDefaultInputs, type Inputs } from './inputCore';
3+
import { getInputsFromPrompts } from './inputPrompts';
4+
import type { Language } from './localization';
5+
6+
export async function getInputs(language: Language): Promise<Inputs> {
7+
const argInputs = getInputsFromArgs();
8+
const defaultInputs = getDefaultInputs(argInputs);
9+
10+
if (argInputs.useDefaults) {
11+
return defaultInputs;
12+
}
13+
14+
return getInputsFromPrompts(language, argInputs);
15+
}

utils/inputArgs.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { parseArgs } from 'node:util';
2+
3+
import { type Inputs } from './inputCore';
4+
import { kebabCase } from './strings';
5+
6+
type ArgInputs = {
7+
address?: string;
8+
anchorProgram: boolean;
9+
clients: Array<'js' | 'rust'>;
10+
force: boolean;
11+
jsClientPackageName?: string;
12+
noClients: boolean;
13+
organizationName?: string;
14+
programCrateName?: string;
15+
programName?: string;
16+
rustClientCrateName?: string;
17+
rustVersion?: string;
18+
shankProgram: boolean;
19+
solanaVersion?: string;
20+
useDefaults: boolean;
21+
targetDirectoryName?: string;
22+
};
23+
24+
export function getInputsFromArgs(): Partial<Inputs> {
25+
const args = process.argv.slice(2);
26+
const { values: options, positionals } = parseArgs({
27+
args,
28+
options: {
29+
address: { type: 'string' },
30+
anchor: { type: 'boolean' },
31+
client: { type: 'string', multiple: true },
32+
default: { type: 'boolean', short: 'd' },
33+
force: { type: 'boolean' },
34+
'js-client-package-name': { type: 'string' },
35+
'no-clients': { type: 'boolean' },
36+
org: { type: 'string' },
37+
'program-crate-name': { type: 'string' },
38+
rust: { type: 'string' },
39+
'rust-client-crate-name': { type: 'string' },
40+
shank: { type: 'boolean' },
41+
solana: { type: 'string' },
42+
},
43+
strict: false,
44+
});
45+
46+
return parseArgInputs({
47+
address: options.address,
48+
anchorProgram: options.anchor ?? false,
49+
clients: options.client,
50+
force: options.force ?? false,
51+
jsClientPackageName: options['js-client-package-name'],
52+
noClients: options['no-clients'] ?? false,
53+
organizationName: options.org,
54+
programCrateName: options['program-crate-name'],
55+
programName: positionals[1],
56+
rustClientCrateName: options['rust-client-crate-name'],
57+
rustVersion: options.rust,
58+
shankProgram: options.shank ?? false,
59+
solanaVersion: options.solana,
60+
useDefaults: options.default ?? false,
61+
targetDirectoryName: positionals[0],
62+
} as ArgInputs);
63+
}
64+
65+
function parseArgInputs(argInputs: ArgInputs): Partial<Inputs> {
66+
const inputs = {} as Partial<Inputs>;
67+
68+
if (argInputs.address) inputs.programAddress = argInputs.address;
69+
if (argInputs.organizationName)
70+
inputs.organizationName = kebabCase(argInputs.organizationName);
71+
if (argInputs.programName)
72+
inputs.programName = kebabCase(argInputs.programName);
73+
if (argInputs.rustVersion) inputs.rustVersion = argInputs.rustVersion;
74+
if (argInputs.solanaVersion) inputs.solanaVersion = argInputs.solanaVersion;
75+
if (argInputs.targetDirectoryName)
76+
inputs.targetDirectoryName = argInputs.targetDirectoryName;
77+
if (argInputs.jsClientPackageName)
78+
inputs.jsClientPackageName = argInputs.jsClientPackageName;
79+
if (argInputs.programCrateName)
80+
inputs.programCrateName = argInputs.programCrateName;
81+
if (argInputs.rustClientCrateName)
82+
inputs.rustClientCrateName = argInputs.rustClientCrateName;
83+
if (argInputs.force) inputs.shouldOverride = true;
84+
if (argInputs.useDefaults) inputs.useDefaults = true;
85+
86+
if (argInputs.anchorProgram) {
87+
inputs.programFramework = 'anchor';
88+
} else if (argInputs.shankProgram) {
89+
inputs.programFramework = 'shank';
90+
}
91+
92+
if (argInputs.noClients) {
93+
inputs.jsClient = false;
94+
inputs.rustClient = false;
95+
} else if (argInputs.clients) {
96+
inputs.jsClient = argInputs.clients.includes('js');
97+
inputs.rustClient = argInputs.clients.includes('rust');
98+
}
99+
100+
return inputs;
101+
}

utils/inputCore.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as fs from 'node:fs';
2+
3+
import { Language } from './localization';
4+
import { kebabCase } from './strings';
5+
6+
export const allClients = ['js', 'rust'] as const;
7+
export type Client = (typeof allClients)[number];
8+
9+
export type Inputs = {
10+
jsClient: boolean;
11+
jsClientPackageName: string;
12+
organizationName: string;
13+
programAddress?: string;
14+
programCrateName: string;
15+
programFramework: 'shank' | 'anchor';
16+
programName: string;
17+
rustClient: boolean;
18+
rustClientCrateName: string;
19+
rustVersion?: string;
20+
shouldOverride: boolean;
21+
solanaVersion?: string;
22+
targetDirectoryName: string;
23+
useDefaults: boolean;
24+
};
25+
26+
// export async function getInputs(language: Language): Promise<Inputs> {
27+
// const argInputs = getInputsFromArgs();
28+
// const defaultInputs = getDefaultInputs(argInputs);
29+
30+
// if (argInputs.useDefaults) {
31+
// return defaultInputs;
32+
// }
33+
34+
// return getInputsFromPrompts(language, argInputs);
35+
// }
36+
37+
export function getDefaultInputs(partialInputs: Partial<Inputs>): Inputs {
38+
const organizationName = kebabCase(
39+
partialInputs.organizationName ?? 'solana-program'
40+
);
41+
const parsedTargetDirectoryName = partialInputs.targetDirectoryName
42+
? partialInputs.targetDirectoryName.split('/').pop()
43+
: '';
44+
const programName = kebabCase(
45+
partialInputs.programName ?? (parsedTargetDirectoryName || 'my-program')
46+
);
47+
const programCrateName =
48+
partialInputs.programCrateName ?? `${organizationName}-${programName}`;
49+
50+
return {
51+
jsClient: true,
52+
jsClientPackageName: `@${organizationName}/${programName}`,
53+
organizationName,
54+
programCrateName,
55+
programFramework: 'shank',
56+
programName,
57+
rustClient: true,
58+
rustClientCrateName: `${programCrateName}-client`,
59+
shouldOverride: false,
60+
targetDirectoryName: programName,
61+
useDefaults: false,
62+
...partialInputs,
63+
};
64+
}
65+
66+
function canSkipEmptying(dir: fs.PathLike) {
67+
if (!fs.existsSync(dir)) {
68+
return true;
69+
}
70+
71+
const files = fs.readdirSync(dir);
72+
if (files.length === 0) {
73+
return true;
74+
}
75+
if (files.length === 1 && files[0] === '.git') {
76+
return true;
77+
}
78+
79+
return false;
80+
}

0 commit comments

Comments
 (0)