@torpor/unplugin 1.0.4 → 1.1.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;cAQa,iBAAiB,gBAAgB;cA2GjC,UAAU,iBAAiB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;cAQa,iBAAiB,gBAAgB;cA4IjC,UAAU,iBAAiB"}
package/dist/index.mjs CHANGED
@@ -15,19 +15,19 @@ const unpluginFactory = (options) => ({
15
15
  return /\.torp\?*/.test(id);
16
16
  },
17
17
  transform(code, id, viteOptions) {
18
- if (viteOptions && viteOptions.dev !== void 0) {
19
- options ??= {};
20
- options.dev = viteOptions.dev;
21
- }
22
- if (viteOptions && viteOptions.ssr !== void 0) {
23
- options ??= {};
24
- options.server = viteOptions.ssr;
18
+ let transformOptions = { ...options };
19
+ if (viteOptions && viteOptions.dev !== void 0) transformOptions.dev = viteOptions.dev;
20
+ const query = getQuery(id);
21
+ if (query?.has("client")) transformOptions.server = false;
22
+ else if (query?.has("server")) transformOptions.server = true;
23
+ else {
24
+ if (viteOptions && viteOptions.ssr !== void 0) transformOptions.server = viteOptions.ssr;
25
+ if (transformOptions.test) transformOptions.server = true;
25
26
  }
26
- if (options?.test) options.server = true;
27
27
  let parsed = parse(code);
28
- if (parsed.ok && parsed.template) return transform(parsed.template, id, options);
28
+ if (parsed.ok && parsed.template) return transform(parsed.template, id, transformOptions);
29
29
  else {
30
- let name = id.split(/[\\/]/).at(-1).replace(/\.torp$/, "");
30
+ let name = id.split(/[\\/]/).at(-1).replace(/\.torp.*$/, "");
31
31
  let errorMessages = parsed.errors.map((e) => `${e.startLine + 1},${e.startChar}: ${e.message}`);
32
32
  console.log(`\nERRORS: ${id}\n======\n${errorMessages.join("\n")}`);
33
33
  let errorCode = `
@@ -44,14 +44,23 @@ export default function Error() {
44
44
  }
45
45
  }`;
46
46
  let errorParsed = parse(errorCode);
47
- if (errorParsed.ok && errorParsed.template) return transform(errorParsed.template, id, options);
47
+ if (errorParsed.ok && errorParsed.template) return transform(errorParsed.template, id, transformOptions);
48
48
  throw new Error(`Parse failed for ${id}, ${errorMessages.join("\n")}`);
49
49
  }
50
50
  }
51
51
  });
52
+ /**
53
+ * Gets the query string of a module id (e.g. `client` for `Foo.torp?client`)
54
+ */
55
+ function getQuery(id) {
56
+ const queryStart = id.lastIndexOf("?");
57
+ if (queryStart === -1) return;
58
+ return new URLSearchParams(id.substring(queryStart + 1));
59
+ }
52
60
  function transform(template, id, options) {
53
61
  const built = build(template, options);
54
62
  let transformed = built.code;
63
+ if (options?.test && options.server === false) transformed = transformed.replace(/(from\s*['"])([^'"]+\.torp)(['"])/g, "$1$2?client$3");
55
64
  if (built.styles) for (let style of built.styles) {
56
65
  transformed = `import '${style.hash}.css';\n` + transformed;
57
66
  styles.set(style.hash + ".css", style.style);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Template, build, parse } from \"@torpor/view/compile\";\nimport { type UnpluginFactory, type UnpluginInstance } from \"unplugin\";\nimport { createUnplugin } from \"unplugin\";\nimport { transformWithOxc } from \"vite\";\nimport type Options from \"./types\";\n\nconst styles = new Map<string, string>();\n\nexport const unpluginFactory: UnpluginFactory<Options | undefined> = (options) => ({\n\tname: \"unplugin-torpor\",\n\tresolveId(id /*, importer, options*/) {\n\t\tif (styles.has(id)) {\n\t\t\treturn id;\n\t\t}\n\t\treturn undefined;\n\t},\n\tload(id) {\n\t\tif (styles.has(id)) {\n\t\t\treturn styles.get(id);\n\t\t}\n\t\treturn undefined;\n\t},\n\ttransformInclude(id) {\n\t\t// Check for *.torp files\n\t\treturn /\\.torp\\?*/.test(id);\n\t},\n\t// @ts-ignore\n\ttransform(code, id, viteOptions) {\n\t\t// We may be in dev mode\n\t\tif (viteOptions && viteOptions.dev !== undefined) {\n\t\t\toptions ??= {};\n\t\t\toptions.dev = viteOptions.dev;\n\t\t}\n\n\t\t// Vite can override user server options\n\t\tif (viteOptions && viteOptions.ssr !== undefined) {\n\t\t\toptions ??= {};\n\t\t\toptions.server = viteOptions.ssr;\n\t\t}\n\n\t\t// But when testing we always generate for the server\n\t\tif (options?.test) {\n\t\t\toptions.server = true;\n\t\t}\n\n\t\t// Try to parse the code\n\t\tlet parsed = parse(code);\n\t\tif (parsed.ok && parsed.template) {\n\t\t\t// Transform for server or client\n\t\t\treturn transform(parsed.template, id, options);\n\t\t} else {\n\t\t\t// Show an error component\n\t\t\tlet name = id\n\t\t\t\t.split(/[\\\\/]/)\n\t\t\t\t.at(-1)\n\t\t\t\t.replace(/\\.torp$/, \"\")!;\n\t\t\tlet errorMessages = parsed.errors.map(\n\t\t\t\t(e) => `${e.startLine + 1},${e.startChar}: ${e.message}`,\n\t\t\t);\n\t\t\tconsole.log(`\\nERRORS: ${id}\\n======\\n${errorMessages.join(\"\\n\")}`);\n\t\t\tlet errorCode = `\nexport default function Error() {\n\t@render {\n\t\t<div style=\"background-color: #222; color: #f44\"; font-size: 15px; line-height: 1.5;\">\n\t\t\t<p style=\"margin: 0; padding: 0;\">\n\t\t\t\t<strong>Error${parsed.errors.length === 1 ? \"\" : \"s\"} in ${name}:</strong>\n\t\t\t</p>\n\t\t\t<ul style=\"margin: 0; padding: 0 20px\">\n\t\t\t\t${errorMessages.map((e) => `<li>${e}</li>`).join(\"\\n\")}\n\t\t\t</ul>\n\t\t</div>\n\t}\n}`;\n\t\t\tlet errorParsed = parse(errorCode);\n\t\t\tif (errorParsed.ok && errorParsed.template) {\n\t\t\t\treturn transform(errorParsed.template, id, options);\n\t\t\t}\n\t\t\t// This should never be reached, but just in case...\n\t\t\tthrow new Error(`Parse failed for ${id}, ${errorMessages.join(\"\\n\")}`);\n\t\t}\n\t},\n});\n\nfunction transform(template: Template, id: string, options?: Options) {\n\tconst built = build(template, options);\n\tlet transformed = built.code;\n\n\tif (built.styles) {\n\t\tfor (let style of built.styles) {\n\t\t\t// Add a dynamic import for the component's CSS with a name from\n\t\t\t// the hash and add the styles to a map. Then resolveId will\n\t\t\t// pass the CSS id onto load, which will load the the actual CSS\n\t\t\t// from the map\n\t\t\ttransformed = `import '${style.hash}.css';\\n` + transformed;\n\t\t\tstyles.set(style.hash + \".css\", style.style);\n\t\t}\n\t}\n\n\t//printTransformed(transformed);\n\n\t// TODO: Compile typescript only if script lang=\"ts\" or config.lang=\"ts\"\n\treturn transformWithOxc(transformed, id.replace(/\\.torp.*$/, \".ts\"));\n}\n\n/*\nfunction printTransformed(transformed: string) {\n\tconsole.log(\n\t\ttransformed\n\t\t\t.split(\"\\n\")\n\t\t\t.map((l, i) => `${(i + 1).toString().padEnd(3)} ${l}`)\n\t\t\t.join(\"\\n\"),\n\t);\n}\n*/\n\nexport const unplugin: UnpluginInstance<Options | undefined, boolean> =\n\t/* #__PURE__ */ createUnplugin(unpluginFactory);\n\nexport default unplugin;\n"],"mappings":";;;;AAMA,MAAM,yBAAS,IAAI,IAAoB;AAEvC,MAAa,mBAAyD,aAAa;CAClF,MAAM;CACN,UAAU,IAA4B;EACrC,IAAI,OAAO,IAAI,EAAE,GAChB,OAAO;CAGT;CACA,KAAK,IAAI;EACR,IAAI,OAAO,IAAI,EAAE,GAChB,OAAO,OAAO,IAAI,EAAE;CAGtB;CACA,iBAAiB,IAAI;EAEpB,OAAO,YAAY,KAAK,EAAE;CAC3B;CAEA,UAAU,MAAM,IAAI,aAAa;EAEhC,IAAI,eAAe,YAAY,QAAQ,KAAA,GAAW;GACjD,YAAY,CAAC;GACb,QAAQ,MAAM,YAAY;EAC3B;EAGA,IAAI,eAAe,YAAY,QAAQ,KAAA,GAAW;GACjD,YAAY,CAAC;GACb,QAAQ,SAAS,YAAY;EAC9B;EAGA,IAAI,SAAS,MACZ,QAAQ,SAAS;EAIlB,IAAI,SAAS,MAAM,IAAI;EACvB,IAAI,OAAO,MAAM,OAAO,UAEvB,OAAO,UAAU,OAAO,UAAU,IAAI,OAAO;OACvC;GAEN,IAAI,OAAO,GACT,MAAM,OAAO,CAAC,CACd,GAAG,EAAE,CAAC,CACN,QAAQ,WAAW,EAAE;GACvB,IAAI,gBAAgB,OAAO,OAAO,KAChC,MAAM,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,IAAI,EAAE,SAChD;GACA,QAAQ,IAAI,aAAa,GAAG,YAAY,cAAc,KAAK,IAAI,GAAG;GAClE,IAAI,YAAY;;;;;mBAKA,OAAO,OAAO,WAAW,IAAI,KAAK,IAAI,MAAM,KAAK;;;MAG9D,cAAc,KAAK,MAAM,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE;;;;;GAKxD,IAAI,cAAc,MAAM,SAAS;GACjC,IAAI,YAAY,MAAM,YAAY,UACjC,OAAO,UAAU,YAAY,UAAU,IAAI,OAAO;GAGnD,MAAM,IAAI,MAAM,oBAAoB,GAAG,IAAI,cAAc,KAAK,IAAI,GAAG;EACtE;CACD;AACD;AAEA,SAAS,UAAU,UAAoB,IAAY,SAAmB;CACrE,MAAM,QAAQ,MAAM,UAAU,OAAO;CACrC,IAAI,cAAc,MAAM;CAExB,IAAI,MAAM,QACT,KAAK,IAAI,SAAS,MAAM,QAAQ;EAK/B,cAAc,WAAW,MAAM,KAAK,YAAY;EAChD,OAAO,IAAI,MAAM,OAAO,QAAQ,MAAM,KAAK;CAC5C;CAMD,OAAO,iBAAiB,aAAa,GAAG,QAAQ,aAAa,KAAK,CAAC;AACpE;AAaA,MAAa,WACI,+BAAe,eAAe"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Template, build, parse } from \"@torpor/view/compile\";\nimport { type UnpluginFactory, type UnpluginInstance } from \"unplugin\";\nimport { createUnplugin } from \"unplugin\";\nimport { transformWithOxc } from \"vite\";\nimport type Options from \"./types\";\n\nconst styles = new Map<string, string>();\n\nexport const unpluginFactory: UnpluginFactory<Options | undefined> = (options) => ({\n\tname: \"unplugin-torpor\",\n\tresolveId(id /*, importer, options*/) {\n\t\tif (styles.has(id)) {\n\t\t\treturn id;\n\t\t}\n\t\treturn undefined;\n\t},\n\tload(id) {\n\t\tif (styles.has(id)) {\n\t\t\treturn styles.get(id);\n\t\t}\n\t\treturn undefined;\n\t},\n\ttransformInclude(id) {\n\t\t// Check for *.torp files\n\t\treturn /\\.torp\\?*/.test(id);\n\t},\n\t// @ts-ignore\n\ttransform(code, id, viteOptions) {\n\t\t// Copy the factory options instead of mutating them, so that the\n\t\t// per-request dev/server overrides below don't stick around and\n\t\t// surprise the next request\n\t\tlet transformOptions: Options = { ...options };\n\n\t\t// We may be in dev mode\n\t\tif (viteOptions && viteOptions.dev !== undefined) {\n\t\t\ttransformOptions.dev = viteOptions.dev;\n\t\t}\n\n\t\t// An explicit ?client or ?server query on the import overrides\n\t\t// everything else -- e.g. importing `Component.torp?client` in a test\n\t\t// run compiles the component for the client, so that it can be\n\t\t// mounted, while other components stay SSR-compiled\n\t\tconst query = getQuery(id);\n\t\tif (query?.has(\"client\")) {\n\t\t\ttransformOptions.server = false;\n\t\t} else if (query?.has(\"server\")) {\n\t\t\ttransformOptions.server = true;\n\t\t} else {\n\t\t\t// Vite can override user server options\n\t\t\tif (viteOptions && viteOptions.ssr !== undefined) {\n\t\t\t\ttransformOptions.server = viteOptions.ssr;\n\t\t\t}\n\n\t\t\t// But when testing we always generate for the server\n\t\t\tif (transformOptions.test) {\n\t\t\t\ttransformOptions.server = true;\n\t\t\t}\n\t\t}\n\n\t\t// Try to parse the code\n\t\tlet parsed = parse(code);\n\t\tif (parsed.ok && parsed.template) {\n\t\t\t// Transform for server or client\n\t\t\treturn transform(parsed.template, id, transformOptions);\n\t\t} else {\n\t\t\t// Show an error component\n\t\t\tlet name = id\n\t\t\t\t.split(/[\\\\/]/)\n\t\t\t\t.at(-1)\n\t\t\t\t.replace(/\\.torp.*$/, \"\")!;\n\t\t\tlet errorMessages = parsed.errors.map(\n\t\t\t\t(e) => `${e.startLine + 1},${e.startChar}: ${e.message}`,\n\t\t\t);\n\t\t\tconsole.log(`\\nERRORS: ${id}\\n======\\n${errorMessages.join(\"\\n\")}`);\n\t\t\tlet errorCode = `\nexport default function Error() {\n\t@render {\n\t\t<div style=\"background-color: #222; color: #f44\"; font-size: 15px; line-height: 1.5;\">\n\t\t\t<p style=\"margin: 0; padding: 0;\">\n\t\t\t\t<strong>Error${parsed.errors.length === 1 ? \"\" : \"s\"} in ${name}:</strong>\n\t\t\t</p>\n\t\t\t<ul style=\"margin: 0; padding: 0 20px\">\n\t\t\t\t${errorMessages.map((e) => `<li>${e}</li>`).join(\"\\n\")}\n\t\t\t</ul>\n\t\t</div>\n\t}\n}`;\n\t\t\tlet errorParsed = parse(errorCode);\n\t\t\tif (errorParsed.ok && errorParsed.template) {\n\t\t\t\treturn transform(errorParsed.template, id, transformOptions);\n\t\t\t}\n\t\t\t// This should never be reached, but just in case...\n\t\t\tthrow new Error(`Parse failed for ${id}, ${errorMessages.join(\"\\n\")}`);\n\t\t}\n\t},\n});\n\n/**\n * Gets the query string of a module id (e.g. `client` for `Foo.torp?client`)\n */\nfunction getQuery(id: string): URLSearchParams | undefined {\n\tconst queryStart = id.lastIndexOf(\"?\");\n\tif (queryStart === -1) {\n\t\treturn undefined;\n\t}\n\treturn new URLSearchParams(id.substring(queryStart + 1));\n}\n\nfunction transform(template: Template, id: string, options?: Options) {\n\tconst built = build(template, options);\n\tlet transformed = built.code;\n\n\t// When a component is compiled for the client in a test run, any child\n\t// components it imports must also be compiled for the client -- otherwise\n\t// mounting the parent would try to render SSR children (which don't show\n\t// anything). Pass the ?client query on to imported components\n\tif (options?.test && options.server === false) {\n\t\ttransformed = transformed.replace(/(from\\s*['\"])([^'\"]+\\.torp)(['\"])/g, \"$1$2?client$3\");\n\t}\n\n\tif (built.styles) {\n\t\tfor (let style of built.styles) {\n\t\t\t// Add a dynamic import for the component's CSS with a name from\n\t\t\t// the hash and add the styles to a map. Then resolveId will\n\t\t\t// pass the CSS id onto load, which will load the the actual CSS\n\t\t\t// from the map\n\t\t\ttransformed = `import '${style.hash}.css';\\n` + transformed;\n\t\t\tstyles.set(style.hash + \".css\", style.style);\n\t\t}\n\t}\n\n\t//printTransformed(transformed);\n\n\t// TODO: Compile typescript only if script lang=\"ts\" or config.lang=\"ts\"\n\treturn transformWithOxc(transformed, id.replace(/\\.torp.*$/, \".ts\"));\n}\n\n/*\nfunction printTransformed(transformed: string) {\n\tconsole.log(\n\t\ttransformed\n\t\t\t.split(\"\\n\")\n\t\t\t.map((l, i) => `${(i + 1).toString().padEnd(3)} ${l}`)\n\t\t\t.join(\"\\n\"),\n\t);\n}\n*/\n\nexport const unplugin: UnpluginInstance<Options | undefined, boolean> =\n\t/* #__PURE__ */ createUnplugin(unpluginFactory);\n\nexport default unplugin;\n"],"mappings":";;;;AAMA,MAAM,yBAAS,IAAI,IAAoB;AAEvC,MAAa,mBAAyD,aAAa;CAClF,MAAM;CACN,UAAU,IAA4B;EACrC,IAAI,OAAO,IAAI,EAAE,GAChB,OAAO;CAGT;CACA,KAAK,IAAI;EACR,IAAI,OAAO,IAAI,EAAE,GAChB,OAAO,OAAO,IAAI,EAAE;CAGtB;CACA,iBAAiB,IAAI;EAEpB,OAAO,YAAY,KAAK,EAAE;CAC3B;CAEA,UAAU,MAAM,IAAI,aAAa;EAIhC,IAAI,mBAA4B,EAAE,GAAG,QAAQ;EAG7C,IAAI,eAAe,YAAY,QAAQ,KAAA,GACtC,iBAAiB,MAAM,YAAY;EAOpC,MAAM,QAAQ,SAAS,EAAE;EACzB,IAAI,OAAO,IAAI,QAAQ,GACtB,iBAAiB,SAAS;OACpB,IAAI,OAAO,IAAI,QAAQ,GAC7B,iBAAiB,SAAS;OACpB;GAEN,IAAI,eAAe,YAAY,QAAQ,KAAA,GACtC,iBAAiB,SAAS,YAAY;GAIvC,IAAI,iBAAiB,MACpB,iBAAiB,SAAS;EAE5B;EAGA,IAAI,SAAS,MAAM,IAAI;EACvB,IAAI,OAAO,MAAM,OAAO,UAEvB,OAAO,UAAU,OAAO,UAAU,IAAI,gBAAgB;OAChD;GAEN,IAAI,OAAO,GACT,MAAM,OAAO,CAAC,CACd,GAAG,EAAE,CAAC,CACN,QAAQ,aAAa,EAAE;GACzB,IAAI,gBAAgB,OAAO,OAAO,KAChC,MAAM,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,IAAI,EAAE,SAChD;GACA,QAAQ,IAAI,aAAa,GAAG,YAAY,cAAc,KAAK,IAAI,GAAG;GAClE,IAAI,YAAY;;;;;mBAKA,OAAO,OAAO,WAAW,IAAI,KAAK,IAAI,MAAM,KAAK;;;MAG9D,cAAc,KAAK,MAAM,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE;;;;;GAKxD,IAAI,cAAc,MAAM,SAAS;GACjC,IAAI,YAAY,MAAM,YAAY,UACjC,OAAO,UAAU,YAAY,UAAU,IAAI,gBAAgB;GAG5D,MAAM,IAAI,MAAM,oBAAoB,GAAG,IAAI,cAAc,KAAK,IAAI,GAAG;EACtE;CACD;AACD;;;;AAKA,SAAS,SAAS,IAAyC;CAC1D,MAAM,aAAa,GAAG,YAAY,GAAG;CACrC,IAAI,eAAe,IAClB;CAED,OAAO,IAAI,gBAAgB,GAAG,UAAU,aAAa,CAAC,CAAC;AACxD;AAEA,SAAS,UAAU,UAAoB,IAAY,SAAmB;CACrE,MAAM,QAAQ,MAAM,UAAU,OAAO;CACrC,IAAI,cAAc,MAAM;CAMxB,IAAI,SAAS,QAAQ,QAAQ,WAAW,OACvC,cAAc,YAAY,QAAQ,sCAAsC,eAAe;CAGxF,IAAI,MAAM,QACT,KAAK,IAAI,SAAS,MAAM,QAAQ;EAK/B,cAAc,WAAW,MAAM,KAAK,YAAY;EAChD,OAAO,IAAI,MAAM,OAAO,QAAQ,MAAM,KAAK;CAC5C;CAMD,OAAO,iBAAiB,aAAa,GAAG,QAAQ,aAAa,KAAK,CAAC;AACpE;AAaA,MAAa,WACI,+BAAe,eAAe"}
package/dist/types.d.mts CHANGED
@@ -9,7 +9,12 @@ interface Options {
9
9
  */
10
10
  dev?: boolean;
11
11
  /**
12
- * Whether the plugin is running in a test context
12
+ * Whether the plugin is running in a test context. Components are then
13
+ * compiled for the server by default, so tests can call them as functions
14
+ * and assert on the rendered HTML. To mount a component client-side in
15
+ * the same test project, import it with a `?client` query (e.g.
16
+ * `Component.torp?client`); the override is passed on to any components
17
+ * it imports. A `?server` query does the reverse.
13
18
  */
14
19
  test?: boolean;
15
20
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";UAAyB;;;;EAIxB;;;;EAIA;;;;EAIA;;;;;EAKA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";UAAyB;;;;EAIxB;;;;EAIA;;;;;;;;;EASA;;;;;EAKA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torpor/unplugin",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Unplugin package to compile Torpor components for Vite, Rollup, and other bundlers",
5
5
  "keywords": [
6
6
  "torpor",
@@ -90,7 +90,7 @@
90
90
  },
91
91
  "dependencies": {
92
92
  "unplugin": "^3.3.0",
93
- "@torpor/view": "^1.1.0"
93
+ "@torpor/view": "^1.1.2"
94
94
  },
95
95
  "devDependencies": {
96
96
  "@antfu/eslint-config": "^9.2.0",
@@ -103,6 +103,7 @@
103
103
  "eslint": "^10.8.0",
104
104
  "esno": "^4.8.0",
105
105
  "fast-glob": "^3.3.3",
106
+ "jsdom": "^30.0.1",
106
107
  "nodemon": "^3.1.14",
107
108
  "rimraf": "^6.1.3",
108
109
  "rollup": "^4.62.4",
@@ -118,6 +119,7 @@
118
119
  "lint": "eslint .",
119
120
  "play": "npm -C playground run dev",
120
121
  "release": "bumpp && npm publish",
121
- "start": "esno src/index.ts"
122
+ "start": "esno src/index.ts",
123
+ "test": "vp test"
122
124
  }
123
125
  }