@typesol/cli 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +11 -7
  3. package/dist/cli.js +147 -20
  4. package/dist/clientgen.js +37 -0
  5. package/dist/compiler/analyze.d.ts +11 -1
  6. package/dist/compiler/analyze.js +307 -37
  7. package/dist/compiler/emit/base.d.ts +7 -0
  8. package/dist/compiler/emit/base.js +62 -2
  9. package/dist/compiler/emit/borsh.d.ts +13 -0
  10. package/dist/compiler/emit/borsh.js +232 -0
  11. package/dist/compiler/emit/calls.js +50 -15
  12. package/dist/compiler/emit/cpi.d.ts +32 -8
  13. package/dist/compiler/emit/cpi.js +706 -25
  14. package/dist/compiler/emit/expressions.d.ts +11 -0
  15. package/dist/compiler/emit/expressions.js +126 -4
  16. package/dist/compiler/emit/function.js +45 -12
  17. package/dist/compiler/emit/program.js +24 -6
  18. package/dist/compiler/emit/shared.d.ts +12 -0
  19. package/dist/compiler/emit/shared.js +22 -4
  20. package/dist/compiler/emit/statements.d.ts +13 -0
  21. package/dist/compiler/emit/statements.js +141 -7
  22. package/dist/compiler/emit/storage.d.ts +6 -1
  23. package/dist/compiler/emit/storage.js +82 -17
  24. package/dist/compiler/idl.js +61 -18
  25. package/dist/compiler/model.d.ts +56 -4
  26. package/dist/compiler/model.js +8 -2
  27. package/dist/compiler/toolchain.d.ts +2 -0
  28. package/dist/compiler/toolchain.js +27 -1
  29. package/dist/onchain.d.ts +30 -0
  30. package/dist/onchain.js +53 -0
  31. package/dist/portgen.d.ts +87 -0
  32. package/dist/portgen.js +367 -0
  33. package/dist/templates.js +49 -154
  34. package/dist/testing.d.ts +66 -0
  35. package/dist/testing.js +337 -0
  36. package/dist/workspace.js +11 -1
  37. package/docs/architecture.md +3 -0
  38. package/docs/cli.md +16 -2
  39. package/docs/errors.md +3 -2
  40. package/docs/language.md +19 -14
  41. package/docs/mainnet.md +5 -4
  42. package/docs/migrate-from-anchor.md +113 -0
  43. package/docs/test-your-program.md +52 -0
  44. package/docs/testing.md +1 -1
  45. package/docs/troubleshooting.md +7 -0
  46. package/lib/typesol.d.ts +325 -13
  47. package/package.json +19 -1
  48. package/runtime/typesol_rt.h +54 -9
  49. package/ts-plugin/index.js +112 -0
  50. package/ts-plugin/package.json +4 -0
@@ -34,6 +34,7 @@
34
34
  #define TS_ERR_ALREADY_INITIALIZED 3000 /* AccountDiscriminatorAlreadySet */
35
35
  #define TS_ERR_DISCRIMINATOR 3002 /* AccountDiscriminatorMismatch */
36
36
  #define TS_ERR_ACCOUNT_TOO_SMALL 3003 /* AccountDidNotDeserialize */
37
+ #define TS_ERR_DID_NOT_SERIALIZE 3004 /* AccountDidNotSerialize */
37
38
  #define TS_ERR_TOKEN_ACCOUNT 3003 /* AccountDidNotDeserialize (not an initialized token account) */
38
39
  #define TS_ERR_MINT 3003 /* AccountDidNotDeserialize (not an initialized mint) */
39
40
  #define TS_ERR_NOT_ENOUGH_ACCOUNTS 3005 /* AccountNotEnoughKeys */
@@ -162,8 +163,11 @@ static inline TS_Str ts_str(const uint8_t *ptr, uint64_t len) {
162
163
  static inline bool ts_utf8_valid(const uint8_t *p, uint64_t n) {
163
164
  uint64_t i = 0;
164
165
  while (i < n) {
166
+ /* ASCII eight bytes at a time (no high bit set), then byte by byte up to the next multi-byte sequence. */
167
+ while (i + 8 <= n && (ts_ld64(p + i) & 0x8080808080808080ULL) == 0) i += 8;
168
+ while (i < n && p[i] < 0x80) i++;
169
+ if (i == n) break;
165
170
  uint8_t c = p[i];
166
- if (c < 0x80) { i++; continue; }
167
171
  uint64_t len;
168
172
  uint32_t cp;
169
173
  if (c >= 0xc2 && c <= 0xdf) { len = 2; cp = c & 0x1f; }
@@ -670,16 +674,17 @@ static inline uint64_t ts_realloc(TsParams *params, TsAccount *payer, TsAccount
670
674
  return ts_resize(params, payer, acct, new_len, entry_len);
671
675
  }
672
676
 
677
+ /* `space` for a new account; an existing one needs `min_space` (less for a @layout borsh account). */
673
678
  static inline uint64_t ts_create_pda_if_needed(TsParams *params, TsAccount *payer, TsAccount *acct,
674
679
  SolSignerSeed *seeds, int n, const uint8_t disc[8], uint64_t space,
675
- uint8_t *bump_out, bool *created) {
680
+ uint64_t min_space, uint8_t *bump_out, bool *created) {
676
681
  if (ts_key_eq(&acct->owner, params->program_id)) {
677
682
  SolPubkey derived;
678
683
  uint8_t bump;
679
684
  if (ts_find_pda(seeds, n, params->program_id, &derived, &bump) != SUCCESS ||
680
685
  !ts_key_eq(&derived, &acct->key))
681
686
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
682
- if (acct->data_len < space) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data too small");
687
+ if (acct->data_len < min_space) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data too small");
683
688
  if (!ts_mem_eq(acct->data, disc, 8)) TS_FAIL(TS_ERR_DISCRIMINATOR, "TypeSOL: account discriminator mismatch");
684
689
  *bump_out = bump;
685
690
  *created = false;
@@ -1209,17 +1214,33 @@ static inline uint64_t ts_metadata_address(const SolPubkey *mint, SolPubkey *out
1209
1214
  return SUCCESS;
1210
1215
  }
1211
1216
 
1217
+ static inline uint64_t ts_master_edition_address(const SolPubkey *mint, SolPubkey *out) {
1218
+ SolSignerSeed seeds[4] = {{(const uint8_t *)"metadata", 8},
1219
+ {(const uint8_t *)&TS_MPL_METADATA_ID, 32},
1220
+ {(const uint8_t *)mint, 32},
1221
+ {(const uint8_t *)"edition", 7}};
1222
+ uint8_t bump;
1223
+ if (ts_find_pda(seeds, 4, &TS_MPL_METADATA_ID, out, &bump) != SUCCESS)
1224
+ TS_FAIL(TS_ERR_SEEDS, "TypeSOL: cannot derive the master edition address");
1225
+ return SUCCESS;
1226
+ }
1227
+
1212
1228
  /*
1213
1229
  * Metaplex CreateMetadataAccountV3 (instruction 33): DataV2 { name, symbol, uri, seller_fee_basis_points u16,
1214
- * creators None, collection None, uses None }, is_mutable bool, collection_details None.
1230
+ * creators Option<Vec<Creator>>, collection Option<Collection>, uses None }, is_mutable bool, collection_details
1231
+ * None, or Some(V1 { size: 0 }) when `sized_collection`.
1232
+ * `creators` holds `ncreators` packed Creator records (address, verified, share: 34 bytes); ncreators < 0 is None.
1233
+ * `collection` (NULL for None) is written unverified, as Metaplex requires at creation.
1215
1234
  * Accounts: metadata (w), mint, mint authority (s), payer (ws), update authority, system program.
1216
1235
  */
1217
1236
  static inline uint64_t ts_mpl_create_v3(TsParams *params, const TsAccount *program, TsAccount *metadata,
1218
1237
  TsAccount *mint, TsAccount *mint_authority, TsAccount *payer,
1219
1238
  TsAccount *update_authority, TsAccount *system, TS_Str name, TS_Str symbol,
1220
- TS_Str uri, uint16_t fee_bps, bool is_mutable, const SolSignerSeed *seeds, int n) {
1239
+ TS_Str uri, uint16_t fee_bps, bool is_mutable, const uint8_t *creators, int ncreators,
1240
+ const SolPubkey *collection, bool sized_collection, const SolSignerSeed *seeds, int n) {
1221
1241
  uint8_t *data;
1222
- TS_TRY(ts_alloc(1 + 12 + name.len + symbol.len + uri.len + 2 + 3 + 1 + 1, &data));
1242
+ uint64_t ncr = ncreators > 0 ? (uint64_t)ncreators : 0;
1243
+ TS_TRY(ts_alloc(1 + 12 + name.len + symbol.len + uri.len + 2 + (1 + 4 + 34 * ncr) + (1 + 33) + 1 + 1 + (1 + 9), &data));
1223
1244
  uint64_t at = 0;
1224
1245
  data[at++] = 33;
1225
1246
  ts_put_str(data, &at, name);
@@ -1227,11 +1248,35 @@ static inline uint64_t ts_mpl_create_v3(TsParams *params, const TsAccount *progr
1227
1248
  ts_put_str(data, &at, uri);
1228
1249
  ts_memcpy(data + at, &fee_bps, 2);
1229
1250
  at += 2;
1230
- data[at++] = 0; /* creators: None */
1231
- data[at++] = 0; /* collection: None */
1251
+ if (ncreators < 0) {
1252
+ data[at++] = 0; /* creators: None */
1253
+ } else {
1254
+ data[at++] = 1;
1255
+ uint32_t count = (uint32_t)ncreators;
1256
+ ts_memcpy(data + at, &count, 4);
1257
+ at += 4;
1258
+ ts_memcpy(data + at, creators, 34 * ncr);
1259
+ at += 34 * ncr;
1260
+ }
1261
+ if (collection) {
1262
+ data[at++] = 1;
1263
+ data[at++] = 0; /* verified: false */
1264
+ ts_memcpy(data + at, collection, 32);
1265
+ at += 32;
1266
+ } else {
1267
+ data[at++] = 0; /* collection: None */
1268
+ }
1232
1269
  data[at++] = 0; /* uses: None */
1233
1270
  data[at++] = is_mutable ? 1 : 0;
1234
- data[at++] = 0; /* collection_details: None */
1271
+ if (sized_collection) {
1272
+ /* collection_details: Some(V1 { size: 0 }): this NFT is a collection items can be verified into */
1273
+ data[at++] = 1;
1274
+ data[at++] = 0;
1275
+ ts_memzero(data + at, 8);
1276
+ at += 8;
1277
+ } else {
1278
+ data[at++] = 0; /* collection_details: None */
1279
+ }
1235
1280
  bool ua_signs = update_authority->is_signer || ts_key_eq(&update_authority->key, &mint_authority->key);
1236
1281
  SolAccountMeta metas[6] = {{&metadata->key, true, false}, {&mint->key, false, false},
1237
1282
  {&mint_authority->key, false, true}, {&payer->key, true, true},
@@ -0,0 +1,112 @@
1
+ // TypeScript language-service plugin: TypeSOL's own errors in the editor, next to TypeScript's.
2
+ //
3
+ // programs/tsconfig.json: { "compilerOptions": { …, "plugins": [{ "name": "typesol/ts-plugin" }] } }
4
+ //
5
+ // For each program entry of the project (typesol.json), once TypeScript itself reports no errors, it runs
6
+ // `typesol check --json --stdin` on the editor's text (unsaved changes included) and shows what the compiler refuses
7
+ // (an unsupported construct, a missing account constraint…) at its line and column. Editors load tsconfig plugins
8
+ // from the workspace's TypeScript (VS Code: "TypeScript: Select TypeScript Version" → "Use Workspace Version").
9
+ // CommonJS on purpose: tsserver loads plugins with require().
10
+ "use strict";
11
+ const { execFileSync } = require("node:child_process");
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+
15
+ const CLI = path.join(__dirname, "..", "dist", "cli.js");
16
+ const CODE = 9900;
17
+
18
+ /** The program entry files of the typesol.json at or above `dir` (absolute), or an empty set. */
19
+ function entriesNear(dir) {
20
+ for (let d = dir; ; d = path.dirname(d)) {
21
+ const config = path.join(d, "typesol.json");
22
+ if (fs.existsSync(config)) {
23
+ try {
24
+ const programs = JSON.parse(fs.readFileSync(config, "utf8")).programs || {};
25
+ return new Set(Object.values(programs).map((p) => path.resolve(d, typeof p === "string" ? p : p.path)));
26
+ } catch {
27
+ return new Set();
28
+ }
29
+ }
30
+ if (path.dirname(d) === d) return new Set();
31
+ }
32
+ }
33
+
34
+ function init({ typescript: ts }) {
35
+ function create(info) {
36
+ const ls = info.languageService;
37
+ const log = (m) => info.project.projectService.logger.info(`[typesol] ${m}`);
38
+ const cache = new Map(); // fileName -> { text, found }
39
+
40
+ /** The entry's text plus the modification times of the .ts files beside it (its imports usually live there). */
41
+ function key(fileName, sf) {
42
+ const dir = path.dirname(path.resolve(fileName));
43
+ let stamps = "";
44
+ try {
45
+ for (const f of fs.readdirSync(dir).sort())
46
+ if (f.endsWith(".ts") && path.join(dir, f) !== path.resolve(fileName)) stamps += `${f}:${fs.statSync(path.join(dir, f)).mtimeMs};`;
47
+ } catch {
48
+ stamps = "";
49
+ }
50
+ return `${stamps}\n${sf.text}`;
51
+ }
52
+
53
+ function typesolDiagnostics(fileName, sf) {
54
+ const k = key(fileName, sf);
55
+ const hit = cache.get(fileName);
56
+ if (hit && hit.key === k) return hit.found;
57
+ let found = [];
58
+ try {
59
+ const out = execFileSync(process.execPath, [CLI, "check", fileName, "--json", "--stdin"], {
60
+ input: sf.text,
61
+ encoding: "utf8",
62
+ timeout: 15000,
63
+ // tsserver may run inside an editor's Electron: make it behave as plain Node for the child.
64
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" },
65
+ stdio: ["pipe", "pipe", "pipe"],
66
+ });
67
+ found = JSON.parse(out).diagnostics;
68
+ } catch (e) {
69
+ // Exit 1 with JSON on stdout means diagnostics; anything else is a crash worth logging, not showing.
70
+ try {
71
+ found = JSON.parse(String(e.stdout)).diagnostics;
72
+ } catch {
73
+ log(`check failed for ${fileName}: ${e.message}`);
74
+ found = [];
75
+ }
76
+ }
77
+ cache.set(fileName, { key: k, found });
78
+ return found;
79
+ }
80
+
81
+ const proxy = Object.create(null);
82
+ for (const k of Object.keys(ls)) proxy[k] = (...args) => ls[k](...args);
83
+ proxy.getSemanticDiagnostics = (fileName) => {
84
+ const prior = ls.getSemanticDiagnostics(fileName);
85
+ if (prior.some((d) => d.category === ts.DiagnosticCategory.Error)) return prior; // TypeScript's errors first
86
+ if (!entriesNear(path.dirname(path.resolve(fileName))).has(path.resolve(fileName))) return prior;
87
+ const sf = ls.getProgram() && ls.getProgram().getSourceFile(fileName);
88
+ if (!sf) return prior;
89
+ const own = typesolDiagnostics(fileName, sf)
90
+ .filter((d) => path.resolve(d.file) === path.resolve(fileName))
91
+ .map((d) => {
92
+ const line = Math.max(0, Math.min(d.line - 1, sf.getLineStarts().length - 1));
93
+ const start = Math.min(sf.getPositionOfLineAndCharacter(line, 0) + Math.max(0, d.column - 1), sf.end);
94
+ const word = /^[\w$]+/.exec(sf.text.slice(start));
95
+ return {
96
+ file: sf,
97
+ start,
98
+ length: word ? word[0].length : 1,
99
+ messageText: d.message,
100
+ category: ts.DiagnosticCategory.Error,
101
+ code: CODE,
102
+ source: "typesol",
103
+ };
104
+ });
105
+ return [...prior, ...own];
106
+ };
107
+ return proxy;
108
+ }
109
+ return { create };
110
+ }
111
+
112
+ module.exports = init;
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "commonjs",
3
+ "main": "index.js"
4
+ }