@motionscript/cli 0.1.0-alpha.0 → 0.1.0-alpha.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @motionscript/cli
2
2
 
3
+ ## 0.1.0-alpha.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 5b5dc04: `ms add` writes only inside the components folder. A registry file's `path` was joined onto `paths.components` unchecked, so an item declaring `../../../escaped.txt`, or an absolute path, wrote outside the project. Such a path is now refused with an error naming the item and the path, as is a `paths.components` outside the project, and every requested item is checked before any file is written.
8
+
9
+ ## 0.1.0-alpha.2
10
+
11
+ ## 0.1.0-alpha.1
12
+
3
13
  ## 0.1.0-alpha.0
4
14
 
5
15
  Initial release.
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/registry/add.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAQrC;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0F1F"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/registry/add.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAQrC;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA6F1F"}
@@ -3,7 +3,7 @@ import kleur from 'kleur';
3
3
  import prompts from 'prompts';
4
4
  import { componentsConfigExists, loadComponentsConfig } from './config.js';
5
5
  import { fetchRegistryIndex, resolveItemUrl } from './fetch.js';
6
- import { resolveComponents, writeComponentFiles, collectDependencies } from './install.js';
6
+ import { componentDestinations, resolveComponents, writeComponentFiles, collectDependencies } from './install.js';
7
7
  import { detectPackageManager, installDependencies } from './package-manager.js';
8
8
  import { runInit } from './init.js';
9
9
  import { loadLock, recordInstall, writeLock } from './lock.js';
@@ -58,6 +58,10 @@ export async function runAdd(projectRoot, argv) {
58
58
  }
59
59
  console.log(kleur.bold(`Adding ${names.join(', ')}...`));
60
60
  const items = await resolveComponents(names, config, defaultRegistryUrl);
61
+ // Every item, before any is written, so one item's bad path leaves no
62
+ // half-installed set behind.
63
+ for (const item of items)
64
+ componentDestinations(item, config, projectRoot);
61
65
  const lock = loadLock(projectRoot);
62
66
  for (const item of items) {
63
67
  // The name as typed, so `@acme/gauge` is recorded under its namespace
@@ -1 +1 @@
1
- {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/registry/add.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport kleur from 'kleur';\nimport prompts from 'prompts';\nimport type minimist from 'minimist';\nimport { componentsConfigExists, loadComponentsConfig } from './config.js';\nimport { fetchRegistryIndex, resolveItemUrl } from './fetch.js';\nimport { resolveComponents, writeComponentFiles, collectDependencies } from './install.js';\nimport { detectPackageManager, installDependencies } from './package-manager.js';\nimport { runInit } from './init.js';\nimport { loadLock, recordInstall, writeLock } from './lock.js';\n\n/**\n * `ms add [name...]` — copy one or more registry components' source into\n * this project, following any `registryDependencies` first, then install the\n * npm packages they need.\n */\nexport async function runAdd(projectRoot: string, argv: minimist.ParsedArgs): Promise<void> {\n if (!componentsConfigExists(projectRoot)) {\n if (!argv.yes) {\n const { proceed } = await prompts({\n type: 'confirm',\n name: 'proceed',\n message: 'No motionscript.json found. Run `ms init` now?',\n initial: true,\n });\n if (!proceed) {\n console.log(kleur.dim('Aborted.'));\n return;\n }\n }\n await runInit(projectRoot, argv);\n }\n\n const config = loadComponentsConfig(projectRoot);\n if (!config) throw new Error('motionscript.json could not be loaded after init.');\n\n const defaultRegistryUrl = typeof argv.registry === 'string' ? argv.registry : undefined;\n\n let names = (argv._.slice(1) as string[]).filter(Boolean);\n if (names.length === 0) {\n const index = await fetchRegistryIndex(defaultRegistryUrl);\n if (index.length === 0) {\n console.log(kleur.yellow('The registry has no components to add.'));\n return;\n }\n const { selected } = await prompts({\n type: 'multiselect',\n name: 'selected',\n message: 'Select components to add',\n choices: index.map(entry => ({\n title: entry.name,\n description: entry.description,\n value: entry.name,\n })),\n min: 1,\n });\n if (!selected || (selected as string[]).length === 0) {\n console.log(kleur.dim('Aborted.'));\n return;\n }\n names = selected as string[];\n }\n\n console.log(kleur.bold(`Adding ${names.join(', ')}...`));\n const items = await resolveComponents(names, config, defaultRegistryUrl);\n const lock = loadLock(projectRoot);\n\n for (const item of items) {\n // The name as typed, so `@acme/gauge` is recorded under its namespace\n // rather than under the bare `gauge` two registries could both publish.\n const requested = names.find((n) => n.endsWith(item.name)) ?? item.name;\n const { written, skipped, recorded } = await writeComponentFiles(item, config, projectRoot, {\n // --yes is \"fully non-interactive\": it also answers the overwrite\n // prompt, so a scripted `ms add --yes` never blocks on stdin.\n force: Boolean(argv.overwrite || argv.yes),\n previous: lock.components[requested],\n });\n if (Object.keys(recorded).length > 0) {\n recordInstall(\n lock,\n requested,\n item,\n resolveItemUrl(requested, config, defaultRegistryUrl).url,\n recorded,\n );\n }\n for (const file of written) {\n console.log(` ${kleur.green('✓')} ${path.relative(projectRoot, file)}`);\n }\n for (const file of skipped) {\n console.log(` ${kleur.dim('–')} ${path.relative(projectRoot, file)} ${kleur.dim('(skipped)')}`);\n }\n }\n\n if (!argv['skip-install']) {\n const dependencies = collectDependencies(items);\n if (Object.keys(dependencies).length > 0) {\n const pm = detectPackageManager(projectRoot);\n console.log(kleur.bold(`Installing dependencies with ${pm}...`));\n installDependencies(pm, dependencies, projectRoot);\n }\n }\n\n writeLock(projectRoot, lock);\n\n console.log(kleur.green(`Done. Added: ${items.map(i => i.name).join(', ')}.`));\n}\n"]}
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/registry/add.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAClH,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE/D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,WAAmB,EAAE,IAAyB;IACvE,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACZ,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC;gBAC9B,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,gDAAgD;gBACzD,OAAO,EAAE,IAAI;aAChB,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,EAAE,CAAC;gBACX,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;gBACnC,OAAO;YACX,CAAC;QACL,CAAC;QACD,MAAM,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAElF,MAAM,kBAAkB,GAAG,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAEzF,IAAI,KAAK,GAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;QAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,wCAAwC,CAAC,CAAC,CAAC;YACpE,OAAO;QACX,CAAC;QACD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAC;YAC/B,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,0BAA0B;YACnC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACzB,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,KAAK,EAAE,KAAK,CAAC,IAAI;aACpB,CAAC,CAAC;YACH,GAAG,EAAE,CAAC;SACT,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,IAAK,QAAqB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;YACnC,OAAO;QACX,CAAC;QACD,KAAK,GAAG,QAAoB,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzE,sEAAsE;IACtE,6BAA6B;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,sEAAsE;QACtE,wEAAwE;QACxE,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;QACxE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE;YACxF,kEAAkE;YAClE,8DAA8D;YAC9D,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC;YAC1C,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;SACvC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnC,aAAa,CACT,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC,GAAG,EACzD,QAAQ,CACX,CAAC;QACN,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACrG,CAAC;IACL,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QACxB,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAChD,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,EAAE,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;YAC7C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC,CAAC;YACjE,mBAAmB,CAAC,EAAE,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;QACvD,CAAC;IACL,CAAC;IAED,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAE7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gBAAgB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACnF,CAAC","sourcesContent":["import path from 'node:path';\nimport kleur from 'kleur';\nimport prompts from 'prompts';\nimport type minimist from 'minimist';\nimport { componentsConfigExists, loadComponentsConfig } from './config.js';\nimport { fetchRegistryIndex, resolveItemUrl } from './fetch.js';\nimport { componentDestinations, resolveComponents, writeComponentFiles, collectDependencies } from './install.js';\nimport { detectPackageManager, installDependencies } from './package-manager.js';\nimport { runInit } from './init.js';\nimport { loadLock, recordInstall, writeLock } from './lock.js';\n\n/**\n * `ms add [name...]` — copy one or more registry components' source into\n * this project, following any `registryDependencies` first, then install the\n * npm packages they need.\n */\nexport async function runAdd(projectRoot: string, argv: minimist.ParsedArgs): Promise<void> {\n if (!componentsConfigExists(projectRoot)) {\n if (!argv.yes) {\n const { proceed } = await prompts({\n type: 'confirm',\n name: 'proceed',\n message: 'No motionscript.json found. Run `ms init` now?',\n initial: true,\n });\n if (!proceed) {\n console.log(kleur.dim('Aborted.'));\n return;\n }\n }\n await runInit(projectRoot, argv);\n }\n\n const config = loadComponentsConfig(projectRoot);\n if (!config) throw new Error('motionscript.json could not be loaded after init.');\n\n const defaultRegistryUrl = typeof argv.registry === 'string' ? argv.registry : undefined;\n\n let names = (argv._.slice(1) as string[]).filter(Boolean);\n if (names.length === 0) {\n const index = await fetchRegistryIndex(defaultRegistryUrl);\n if (index.length === 0) {\n console.log(kleur.yellow('The registry has no components to add.'));\n return;\n }\n const { selected } = await prompts({\n type: 'multiselect',\n name: 'selected',\n message: 'Select components to add',\n choices: index.map(entry => ({\n title: entry.name,\n description: entry.description,\n value: entry.name,\n })),\n min: 1,\n });\n if (!selected || (selected as string[]).length === 0) {\n console.log(kleur.dim('Aborted.'));\n return;\n }\n names = selected as string[];\n }\n\n console.log(kleur.bold(`Adding ${names.join(', ')}...`));\n const items = await resolveComponents(names, config, defaultRegistryUrl);\n // Every item, before any is written, so one item's bad path leaves no\n // half-installed set behind.\n for (const item of items) componentDestinations(item, config, projectRoot);\n const lock = loadLock(projectRoot);\n\n for (const item of items) {\n // The name as typed, so `@acme/gauge` is recorded under its namespace\n // rather than under the bare `gauge` two registries could both publish.\n const requested = names.find((n) => n.endsWith(item.name)) ?? item.name;\n const { written, skipped, recorded } = await writeComponentFiles(item, config, projectRoot, {\n // --yes is \"fully non-interactive\": it also answers the overwrite\n // prompt, so a scripted `ms add --yes` never blocks on stdin.\n force: Boolean(argv.overwrite || argv.yes),\n previous: lock.components[requested],\n });\n if (Object.keys(recorded).length > 0) {\n recordInstall(\n lock,\n requested,\n item,\n resolveItemUrl(requested, config, defaultRegistryUrl).url,\n recorded,\n );\n }\n for (const file of written) {\n console.log(` ${kleur.green('✓')} ${path.relative(projectRoot, file)}`);\n }\n for (const file of skipped) {\n console.log(` ${kleur.dim('–')} ${path.relative(projectRoot, file)} ${kleur.dim('(skipped)')}`);\n }\n }\n\n if (!argv['skip-install']) {\n const dependencies = collectDependencies(items);\n if (Object.keys(dependencies).length > 0) {\n const pm = detectPackageManager(projectRoot);\n console.log(kleur.bold(`Installing dependencies with ${pm}...`));\n installDependencies(pm, dependencies, projectRoot);\n }\n }\n\n writeLock(projectRoot, lock);\n\n console.log(kleur.green(`Done. Added: ${items.map(i => i.name).join(', ')}.`));\n}\n"]}
@@ -26,12 +26,20 @@ export interface WriteResult {
26
26
  * that genuinely needs several files declares them nested
27
27
  * (`"line-chart/chart.ts"`, `"line-chart/axis.ts"`) and lands in a real
28
28
  * subfolder. The registry item's own file paths are the only thing that
29
- * decides the shape — this function just joins them onto the components root.
29
+ * decides the shape, within the components root (see `componentDestinations`).
30
30
  */
31
31
  export declare function writeComponentFiles(item: RegistryItem, config: ComponentsConfig, projectRoot: string, options?: {
32
32
  force?: boolean;
33
33
  previous?: LockedComponent;
34
34
  }): Promise<WriteResult>;
35
+ /**
36
+ * Where each of `item`'s files lands, keyed by the path it declares.
37
+ *
38
+ * A registry is fetched, so its paths are input: one that is absolute, or that
39
+ * leaves the components root once resolved, throws before anything is written,
40
+ * as does a components root outside the project.
41
+ */
42
+ export declare function componentDestinations(item: RegistryItem, config: ComponentsConfig, projectRoot: string): Map<string, string>;
35
43
  /** Merge every installed item's npm `dependencies` ("pkg@range" strings) into one deduped map. */
36
44
  export declare function collectDependencies(items: RegistryItem[]): Record<string, string>;
37
45
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/registry/install.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5D;;;;GAIG;AACH,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAAC,YAAY,EAAE,CAAC,CAkBzB;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACrC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAO,GAC9D,OAAO,CAAC,WAAW,CAAC,CAkDtB;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAajF"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/registry/install.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5D;;;;GAIG;AACH,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAAC,YAAY,EAAE,CAAC,CAkBzB;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACrC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAO,GAC9D,OAAO,CAAC,WAAW,CAAC,CAkDtB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBrB;AAQD,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAajF"}
@@ -37,15 +37,15 @@ export async function resolveComponents(names, config, defaultRegistryUrl) {
37
37
  * that genuinely needs several files declares them nested
38
38
  * (`"line-chart/chart.ts"`, `"line-chart/axis.ts"`) and lands in a real
39
39
  * subfolder. The registry item's own file paths are the only thing that
40
- * decides the shape — this function just joins them onto the components root.
40
+ * decides the shape, within the components root (see `componentDestinations`).
41
41
  */
42
42
  export async function writeComponentFiles(item, config, projectRoot, options = {}) {
43
- const targetDir = path.resolve(projectRoot, config.paths.components);
43
+ const destinations = componentDestinations(item, config, projectRoot);
44
44
  const written = [];
45
45
  const skipped = [];
46
46
  const recorded = {};
47
47
  for (const file of item.files) {
48
- const dest = path.join(targetDir, file.path);
48
+ const dest = destinations.get(file.path);
49
49
  if (fs.existsSync(dest) && !options.force) {
50
50
  // The lockfile is what makes this prompt worth reading. "Already
51
51
  // exists" is true of every file on a re-install and says nothing;
@@ -86,6 +86,36 @@ export async function writeComponentFiles(item, config, projectRoot, options = {
86
86
  }
87
87
  return { written, skipped, recorded };
88
88
  }
89
+ /**
90
+ * Where each of `item`'s files lands, keyed by the path it declares.
91
+ *
92
+ * A registry is fetched, so its paths are input: one that is absolute, or that
93
+ * leaves the components root once resolved, throws before anything is written,
94
+ * as does a components root outside the project.
95
+ */
96
+ export function componentDestinations(item, config, projectRoot) {
97
+ const root = path.resolve(projectRoot);
98
+ const targetDir = path.resolve(root, config.paths.components);
99
+ if (!isWithin(root, targetDir)) {
100
+ throw new Error(`paths.components "${config.paths.components}" in motionscript.json is outside the project (${root}).`);
101
+ }
102
+ const destinations = new Map();
103
+ for (const file of item.files) {
104
+ const dest = path.resolve(targetDir, file.path);
105
+ const absolute = path.posix.isAbsolute(file.path) || path.win32.isAbsolute(file.path);
106
+ if (absolute || dest === targetDir || !isWithin(targetDir, dest)) {
107
+ throw new Error(`Registry item "${item.name}" declares a file at "${file.path}", outside ${config.paths.components}/. ` +
108
+ 'A registry file path must be relative to the components folder and stay inside it.');
109
+ }
110
+ destinations.set(file.path, dest);
111
+ }
112
+ return destinations;
113
+ }
114
+ /** Whether `file` is `dir` or below it, by resolved path rather than by string prefix. */
115
+ function isWithin(dir, file) {
116
+ const rel = path.relative(dir, file);
117
+ return rel === '' || (rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel));
118
+ }
89
119
  /** Merge every installed item's npm `dependencies` ("pkg@range" strings) into one deduped map. */
90
120
  export function collectDependencies(items) {
91
121
  const deps = {};
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/registry/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAwB,MAAM,WAAW,CAAC;AAE5D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,KAAe,EACf,MAA+B,EAC/B,kBAAsC;IAEtC,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,UAAU,KAAK,CAAC,IAAY;QAC7B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;QACvE,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,oBAAoB,IAAI,EAAE,EAAE,CAAC;YAChD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AASD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,IAAkB,EAClB,MAAwB,EACxB,WAAmB,EACnB,UAA2D,EAAE;IAE7D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACrE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAwD,EAAE,CAAC;IAEzE,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACxC,iEAAiE;YACjE,kEAAkE;YAClE,8DAA8D;YAC9D,kCAAkC;YAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACtE,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;gBACxB,iEAAiE;gBACjE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;oBAClB,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;oBACxC,OAAO,EAAE,IAAI,CAAC,OAAO;iBACxB,CAAC;gBACF,SAAS;YACb,CAAC;YACD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC;gBAChC,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,WAAW;gBACjB,OAAO,EACH,KAAK,KAAK,UAAU;oBAChB,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,4BAC/B,OAAO,CAAC,QAAQ,EAAE,OAAO,IAAI,kBACjC,cAAc;oBAChB,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,6BAA6B;gBAC1E,OAAO,EAAE,KAAK;aACjB,CAAC,CAAC;YACH,IAAI,CAAC,SAAS,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,SAAS;YACb,CAAC;QACL,CAAC;QACD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAClB,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;YACxC,OAAO,EAAE,IAAI,CAAC,OAAO;SACxB,CAAC;IACN,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC1C,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,KAAqB;IACrD,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;YACzC,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACjC,mEAAmE;YACnE,kEAAkE;YAClE,0DAA0D;YAC1D,IAAI,EAAE,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,IAAI,EAAE,CAAC,CAAC;YACxF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport prompts from 'prompts';\nimport type { ComponentsConfig, RegistryItem } from './protocol.js';\nimport { fetchRegistryItem } from './fetch.js';\nimport { fileState, type LockedComponent } from './lock.js';\n\n/**\n * Resolve a list of requested component names into an ordered, deduped\n * install list, recursively pulling in each item's `registryDependencies`\n * before the item itself (so a dependency's files land first).\n */\nexport async function resolveComponents(\n names: string[],\n config: ComponentsConfig | null,\n defaultRegistryUrl: string | undefined,\n): Promise<RegistryItem[]> {\n const resolved: RegistryItem[] = [];\n const seen = new Set<string>();\n\n async function visit(name: string): Promise<void> {\n if (seen.has(name)) return;\n seen.add(name);\n const item = await fetchRegistryItem(name, config, defaultRegistryUrl);\n for (const dep of item.registryDependencies ?? []) {\n await visit(dep);\n }\n resolved.push(item);\n }\n\n for (const name of names) {\n await visit(name);\n }\n return resolved;\n}\n\nexport interface WriteResult {\n written: string[];\n skipped: string[];\n /** What actually landed, by the path the item declared, for the lockfile. */\n recorded: Record<string, { target: string; content: string }>;\n}\n\n/**\n * Write every file in `item` under `<paths.components>/`, at whatever path\n * each file itself declares — prompting before overwriting an existing file\n * unless `force` is set.\n *\n * Deliberately does *not* nest every item under its own `<item.name>/`\n * subfolder: a simple single-file component declares one file at\n * `\"code.ts\"` and lands flat (`src/components/code.ts`), while a component\n * that genuinely needs several files declares them nested\n * (`\"line-chart/chart.ts\"`, `\"line-chart/axis.ts\"`) and lands in a real\n * subfolder. The registry item's own file paths are the only thing that\n * decides the shape — this function just joins them onto the components root.\n */\nexport async function writeComponentFiles(\n item: RegistryItem,\n config: ComponentsConfig,\n projectRoot: string,\n options: { force?: boolean; previous?: LockedComponent } = {},\n): Promise<WriteResult> {\n const targetDir = path.resolve(projectRoot, config.paths.components);\n const written: string[] = [];\n const skipped: string[] = [];\n const recorded: Record<string, { target: string; content: string }> = {};\n\n for (const file of item.files) {\n const dest = path.join(targetDir, file.path);\n if (fs.existsSync(dest) && !options.force) {\n // The lockfile is what makes this prompt worth reading. \"Already\n // exists\" is true of every file on a re-install and says nothing;\n // \"you changed this since 3.1.0\" is the one fact that decides\n // whether overwriting loses work.\n const previous = options.previous?.files[file.path];\n const state = previous ? fileState(projectRoot, previous) : undefined;\n if (state === 'unchanged') {\n // Untouched since it was installed, so there is nothing to lose.\n fs.writeFileSync(dest, file.content);\n written.push(dest);\n recorded[file.path] = {\n target: path.relative(projectRoot, dest),\n content: file.content,\n };\n continue;\n }\n const { overwrite } = await prompts({\n type: 'confirm',\n name: 'overwrite',\n message:\n state === 'modified'\n ? `${path.relative(projectRoot, dest)} has local changes since ${\n options.previous?.version ?? 'it was installed'\n }. Overwrite?`\n : `${path.relative(projectRoot, dest)} already exists. Overwrite?`,\n initial: false,\n });\n if (!overwrite) {\n skipped.push(dest);\n continue;\n }\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, file.content);\n written.push(dest);\n recorded[file.path] = {\n target: path.relative(projectRoot, dest),\n content: file.content,\n };\n }\n return { written, skipped, recorded };\n}\n\n/** Merge every installed item's npm `dependencies` (\"pkg@range\" strings) into one deduped map. */\nexport function collectDependencies(items: RegistryItem[]): Record<string, string> {\n const deps: Record<string, string> = {};\n for (const item of items) {\n for (const spec of item.dependencies ?? []) {\n const at = spec.lastIndexOf('@');\n // A spec always has a version after the last \"@\" (a scoped package\n // has an earlier \"@\" too, e.g. \"@lezer/javascript@^1.5.4\") — bail\n // rather than silently mis-splitting an unexpected shape.\n if (at <= 0) throw new Error(`Invalid dependency spec (expected \"pkg@range\"): ${spec}`);\n deps[spec.slice(0, at)] = spec.slice(at + 1);\n }\n }\n return deps;\n}\n"]}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/registry/install.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport prompts from 'prompts';\nimport type { ComponentsConfig, RegistryItem } from './protocol.js';\nimport { fetchRegistryItem } from './fetch.js';\nimport { fileState, type LockedComponent } from './lock.js';\n\n/**\n * Resolve a list of requested component names into an ordered, deduped\n * install list, recursively pulling in each item's `registryDependencies`\n * before the item itself (so a dependency's files land first).\n */\nexport async function resolveComponents(\n names: string[],\n config: ComponentsConfig | null,\n defaultRegistryUrl: string | undefined,\n): Promise<RegistryItem[]> {\n const resolved: RegistryItem[] = [];\n const seen = new Set<string>();\n\n async function visit(name: string): Promise<void> {\n if (seen.has(name)) return;\n seen.add(name);\n const item = await fetchRegistryItem(name, config, defaultRegistryUrl);\n for (const dep of item.registryDependencies ?? []) {\n await visit(dep);\n }\n resolved.push(item);\n }\n\n for (const name of names) {\n await visit(name);\n }\n return resolved;\n}\n\nexport interface WriteResult {\n written: string[];\n skipped: string[];\n /** What actually landed, by the path the item declared, for the lockfile. */\n recorded: Record<string, { target: string; content: string }>;\n}\n\n/**\n * Write every file in `item` under `<paths.components>/`, at whatever path\n * each file itself declares — prompting before overwriting an existing file\n * unless `force` is set.\n *\n * Deliberately does *not* nest every item under its own `<item.name>/`\n * subfolder: a simple single-file component declares one file at\n * `\"code.ts\"` and lands flat (`src/components/code.ts`), while a component\n * that genuinely needs several files declares them nested\n * (`\"line-chart/chart.ts\"`, `\"line-chart/axis.ts\"`) and lands in a real\n * subfolder. The registry item's own file paths are the only thing that\n * decides the shape, within the components root (see `componentDestinations`).\n */\nexport async function writeComponentFiles(\n item: RegistryItem,\n config: ComponentsConfig,\n projectRoot: string,\n options: { force?: boolean; previous?: LockedComponent } = {},\n): Promise<WriteResult> {\n const destinations = componentDestinations(item, config, projectRoot);\n const written: string[] = [];\n const skipped: string[] = [];\n const recorded: Record<string, { target: string; content: string }> = {};\n\n for (const file of item.files) {\n const dest = destinations.get(file.path)!;\n if (fs.existsSync(dest) && !options.force) {\n // The lockfile is what makes this prompt worth reading. \"Already\n // exists\" is true of every file on a re-install and says nothing;\n // \"you changed this since 3.1.0\" is the one fact that decides\n // whether overwriting loses work.\n const previous = options.previous?.files[file.path];\n const state = previous ? fileState(projectRoot, previous) : undefined;\n if (state === 'unchanged') {\n // Untouched since it was installed, so there is nothing to lose.\n fs.writeFileSync(dest, file.content);\n written.push(dest);\n recorded[file.path] = {\n target: path.relative(projectRoot, dest),\n content: file.content,\n };\n continue;\n }\n const { overwrite } = await prompts({\n type: 'confirm',\n name: 'overwrite',\n message:\n state === 'modified'\n ? `${path.relative(projectRoot, dest)} has local changes since ${\n options.previous?.version ?? 'it was installed'\n }. Overwrite?`\n : `${path.relative(projectRoot, dest)} already exists. Overwrite?`,\n initial: false,\n });\n if (!overwrite) {\n skipped.push(dest);\n continue;\n }\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, file.content);\n written.push(dest);\n recorded[file.path] = {\n target: path.relative(projectRoot, dest),\n content: file.content,\n };\n }\n return { written, skipped, recorded };\n}\n\n/**\n * Where each of `item`'s files lands, keyed by the path it declares.\n *\n * A registry is fetched, so its paths are input: one that is absolute, or that\n * leaves the components root once resolved, throws before anything is written,\n * as does a components root outside the project.\n */\nexport function componentDestinations(\n item: RegistryItem,\n config: ComponentsConfig,\n projectRoot: string,\n): Map<string, string> {\n const root = path.resolve(projectRoot);\n const targetDir = path.resolve(root, config.paths.components);\n if (!isWithin(root, targetDir)) {\n throw new Error(\n `paths.components \"${config.paths.components}\" in motionscript.json is outside the project (${root}).`,\n );\n }\n const destinations = new Map<string, string>();\n for (const file of item.files) {\n const dest = path.resolve(targetDir, file.path);\n const absolute = path.posix.isAbsolute(file.path) || path.win32.isAbsolute(file.path);\n if (absolute || dest === targetDir || !isWithin(targetDir, dest)) {\n throw new Error(\n `Registry item \"${item.name}\" declares a file at \"${file.path}\", outside ${config.paths.components}/. ` +\n 'A registry file path must be relative to the components folder and stay inside it.',\n );\n }\n destinations.set(file.path, dest);\n }\n return destinations;\n}\n\n/** Whether `file` is `dir` or below it, by resolved path rather than by string prefix. */\nfunction isWithin(dir: string, file: string): boolean {\n const rel = path.relative(dir, file);\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel));\n}\n\n/** Merge every installed item's npm `dependencies` (\"pkg@range\" strings) into one deduped map. */\nexport function collectDependencies(items: RegistryItem[]): Record<string, string> {\n const deps: Record<string, string> = {};\n for (const item of items) {\n for (const spec of item.dependencies ?? []) {\n const at = spec.lastIndexOf('@');\n // A spec always has a version after the last \"@\" (a scoped package\n // has an earlier \"@\" too, e.g. \"@lezer/javascript@^1.5.4\") — bail\n // rather than silently mis-splitting an unexpected shape.\n if (at <= 0) throw new Error(`Invalid dependency spec (expected \"pkg@range\"): ${spec}`);\n deps[spec.slice(0, at)] = spec.slice(at + 1);\n }\n }\n return deps;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionscript/cli",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.3",
4
4
  "type": "module",
5
5
  "description": "Copy Motion Script component source into your project: ms init, ms add, ms list, ms diff.",
6
6
  "repository": {