Skip to content

Commit f1a4d3c

Browse files
committed
feat: Changed npm install to a single string command instead of string + object.
1 parent d6fd648 commit f1a4d3c

8 files changed

Lines changed: 73 additions & 118 deletions

File tree

completions-cron/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"start": "wrangler dev --test-scheduled",
99
"cf-typegen": "wrangler types",
1010
"start:cron": "curl http://localhost:8787/__scheduled?cron=*+*+*+*+*",
11-
"gen-types": "npx supabase gen types typescript --project-id kdctbvqvqjfquplxhqrm > ./types/database.types.d.ts"
11+
"gen-types": "npx supabase gen types typescript --project-id kdctbvqvqjfquplxhqrm > ./types/database.types.d.ts",
12+
"test:completions": "../node_modules/.bin/tsx src/test-completions.ts"
1213
},
1314
"devDependencies": {
1415
"@types/node": "^24.10.1",

completions-cron/src/__generated__/completions-index.ts

Lines changed: 24 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/resources/javascript/npm/completions/npm.globalInstall.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/resources/javascript/npm/completions/npm.install.ts

Lines changed: 7 additions & 0 deletions
Large diffs are not rendered by default.

src/resources/javascript/npm/completions/raw.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/resources/javascript/npm/global-install.ts

Lines changed: 32 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import { ParameterSetting, Plan, StatefulParameter, getPty } from '@codifycli/pl
22

33
import { NpmConfig } from './npm.js';
44

5-
export interface NpmPackage {
6-
name: string;
7-
version?: string;
8-
}
9-
105
interface NpmLsResponse {
116
version: string;
127
name: string;
@@ -17,18 +12,24 @@ interface NpmLsResponse {
1712
}>;
1813
}
1914

20-
export class NpmGlobalInstallParameter extends StatefulParameter<NpmConfig, Array<NpmPackage | string>> {
15+
// Extracts the package name without the version specifier (e.g. "nodemon@3.1.10" → "nodemon")
16+
function packageName(pkg: string): string {
17+
const atIndex = pkg.lastIndexOf('@')
18+
return atIndex > 0 ? pkg.slice(0, atIndex) : pkg
19+
}
20+
21+
export class NpmInstallParameter extends StatefulParameter<NpmConfig, string[]> {
2122

2223
getSettings(): ParameterSetting {
2324
return {
2425
type: 'array',
2526
isElementEqual: this.isEqual,
2627
filterInStatelessMode: (desired, current) =>
27-
current.filter((c) => desired.some((d) => this.isSamePackage(d, c))),
28+
current.filter((c) => desired.some((d) => packageName(d) === packageName(c))),
2829
}
2930
}
3031

31-
async refresh(desired: (NpmPackage | string)[] | null, config: Partial<NpmConfig>): Promise<(NpmPackage | string)[] | null> {
32+
async refresh(desired: string[] | null, config: Partial<NpmConfig>): Promise<string[] | null> {
3233
const pty = getPty();
3334

3435
const { data } = await pty.spawnSafe('npm ls --json --global --depth=0 --loglevel=silent')
@@ -37,106 +38,56 @@ export class NpmGlobalInstallParameter extends StatefulParameter<NpmConfig, Arra
3738
}
3839

3940
const parsedData = JSON.parse(data) as NpmLsResponse;
40-
const dependencies = Object.entries(parsedData.dependencies ?? {})
41-
.map(([name, info]) => ({
42-
name,
43-
version: info.version,
44-
}))
45-
46-
return dependencies.map((c) => {
47-
if (desired?.some((d) => typeof d === 'string' && d === c.name)) {
48-
return c.name;
49-
}
5041

51-
if(desired?.some((d) => typeof d === 'object' && d.name === c.name && !d.version)) {
52-
return { name: c.name };
42+
return Object.entries(parsedData.dependencies ?? {}).map(([name, info]) => {
43+
// If desired entry has a version specifier, return name@version so equality checks work
44+
if (desired?.some((d) => d.includes('@') && packageName(d) === name)) {
45+
return `${name}@${info.version}`
5346
}
54-
55-
return c;
47+
return name
5648
})
5749
}
5850

59-
async add(valueToAdd: Array<NpmPackage | string>, plan: Plan<NpmConfig>): Promise<void> {
51+
async add(valueToAdd: string[], plan: Plan<NpmConfig>): Promise<void> {
6052
await this.install(valueToAdd);
6153
}
6254

63-
async modify(newValue: (NpmPackage | string)[], previousValue: (NpmPackage | string)[], plan: Plan<NpmConfig>): Promise<void> {
64-
const toInstall = newValue.filter((n) => !previousValue.some((p) => this.isSamePackage(n, p)));
65-
const toUninstall = previousValue.filter((p) => !newValue.some((n) => this.isSamePackage(n, p)));
55+
async modify(newValue: string[], previousValue: string[], plan: Plan<NpmConfig>): Promise<void> {
56+
const toInstall = newValue.filter((n) => !previousValue.some((p) => packageName(n) === packageName(p)));
57+
const toUninstall = previousValue.filter((p) => !newValue.some((n) => packageName(n) === packageName(p)));
6658

6759
if (plan.isStateful && toUninstall.length > 0) {
6860
await this.uninstall(toUninstall);
6961
}
7062
await this.install(toInstall);
7163
}
7264

73-
async remove(valueToRemove: (NpmPackage | string)[], plan: Plan<NpmConfig>): Promise<void> {
65+
async remove(valueToRemove: string[], plan: Plan<NpmConfig>): Promise<void> {
7466
await this.uninstall(valueToRemove);
7567
}
7668

77-
async install(packages: Array<NpmPackage | string>): Promise<void> {
78-
const $ = getPty();
79-
const installStatements = packages.map((p) => {
80-
if (typeof p === 'string') {
81-
return p;
82-
}
83-
84-
if (p.version) {
85-
return `${p.name}@${p.version}`;
86-
}
87-
88-
return p.name;
89-
})
90-
91-
if (installStatements.length === 0) {
69+
async install(packages: string[]): Promise<void> {
70+
if (packages.length === 0) {
9271
return;
9372
}
94-
95-
await $.spawn(`npm install --global ${installStatements.join(' ')}`, { interactive: true });
96-
}
97-
98-
async uninstall(packages: Array<NpmPackage | string>): Promise<void> {
9973
const $ = getPty();
100-
const uninstallStatements = packages.map((p) => {
101-
if (typeof p === 'string') {
102-
return p;
103-
}
104-
105-
return p.name;
106-
})
107-
108-
if (uninstallStatements.length === 0) {
109-
return;
110-
}
111-
112-
await $.spawn(`npm uninstall --global ${uninstallStatements.join(' ')}`, { interactive: true });
74+
await $.spawn(`npm install --global ${packages.join(' ')}`, { interactive: true });
11375
}
11476

115-
116-
isSamePackage(desired: NpmPackage | string, current: NpmPackage | string): boolean {
117-
if (typeof desired === 'string' && typeof current === 'string') {
118-
return desired === current;
119-
}
120-
121-
if (typeof desired === 'object' && typeof current === 'object') {
122-
return desired.name === current.name;
77+
async uninstall(packages: string[]): Promise<void> {
78+
if (packages.length === 0) {
79+
return;
12380
}
124-
125-
return false;
81+
const $ = getPty();
82+
await $.spawn(`npm uninstall --global ${packages.map(packageName).join(' ')}`, { interactive: true });
12683
}
12784

128-
isEqual(desired: NpmPackage | string, current: NpmPackage | string): boolean {
129-
if (typeof desired === 'string' && typeof current === 'string') {
130-
return desired === current;
131-
}
132-
133-
if (typeof desired === 'object' && typeof current === 'object') {
134-
return desired.version
135-
? desired.name === current.name && desired.version === current.version
136-
: desired.name === current.name;
85+
isEqual(desired: string, current: string): boolean {
86+
// If no version specified in desired, match by name only
87+
if (!desired.includes('@') || desired.startsWith('@')) {
88+
return packageName(desired) === packageName(current)
13789
}
138-
139-
return false;
90+
return desired === current
14091
}
14192

14293
}

src/resources/javascript/npm/npm-schema.json

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,12 @@
66
"description": "Install and manage packages using NPM.",
77
"type": "object",
88
"properties": {
9-
"globalInstall": {
9+
"install": {
1010
"type": "array",
11-
"description": "An array of",
11+
"description": "An array of npm packages to install globally. Use the npm@version syntax to pin a specific version (e.g. \"nodemon@3.1.10\").",
1212
"items": {
13-
"oneOf": [
14-
{ "type": "string", "description": "Npm packages to install globally" },
15-
{
16-
"type": "object",
17-
"properties": {
18-
"name": { "type": "string", "description": "The name of the package to install" },
19-
"version": { "type": "string", "description": "The version of package to install" }
20-
},
21-
"required": ["name"]
22-
}
23-
]
13+
"type": "string",
14+
"description": "Package name, optionally with a version specifier (e.g. \"typescript\" or \"typescript@5.4.0\")"
2415
}
2516
}
2617
},

src/resources/javascript/npm/npm.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Resource, ResourceSettings, getPty } from '@codifycli/plugin-core';
2-
import { OS, ResourceConfig } from '@codifycli/schemas';
2+
import { OS, ResourceConfig } from '@codifycli/schemas'
33

4-
import { NpmGlobalInstallParameter, NpmPackage } from './global-install.js';
4+
import { NpmInstallParameter } from './global-install.js';
55
import schema from './npm-schema.json'
66

77
export interface NpmConfig extends ResourceConfig {
8-
globalInstall: Array<NpmPackage | string>
8+
install: string[]
99
}
1010

1111
export class Npm extends Resource<NpmConfig> {
@@ -15,7 +15,7 @@ export class Npm extends Resource<NpmConfig> {
1515
operatingSystems: [OS.Darwin, OS.Linux],
1616
schema,
1717
parameterSettings: {
18-
globalInstall: { type: 'stateful', definition: new NpmGlobalInstallParameter() },
18+
install: { type: 'stateful', definition: new NpmInstallParameter() },
1919
},
2020
importAndDestroy: {
2121
preventDestroy: true,

0 commit comments

Comments
 (0)