@imagina-build/ias-cli 0.1.1 → 0.1.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/README.md +4 -4
- package/package.json +1 -1
- package/src/commands/build/local.mjs +17 -5
- package/src/commands/init/index.mjs +12 -9
- package/src/team-vault.mjs +9 -1
package/README.md
CHANGED
|
@@ -31,13 +31,13 @@ npm install --global @imagina-build/ias-cli
|
|
|
31
31
|
|
|
32
32
|
El workflow entra en npm con *trusted publishing* (OIDC, sin token guardado). npmjs.com solo deja que un paquete confíe en un workflow cuando ya existe, así que cada uno se crea antes, a mano y una vez, como un 0.0.0 vacío (`npm login` y `node packages/cli/scripts/publish.mjs --placeholder`); después, en npmjs.com, cada paquete › Settings › Trusted publisher › GitHub Actions: este repositorio y `packages.yml`.
|
|
33
33
|
|
|
34
|
-
`npm pack` a secas no sirve: no se lleva `packages/shared`, que `src/shared.mjs` importa por su ruta (decisión D5). [`scripts/pack.mjs`](scripts/pack.mjs) prepara una copia con `shared/` dentro (y el SDK compilado), la que publica `scripts/publish.mjs` (con `--dry-run`, sin publicar) y la que deja en `.tgz`:
|
|
34
|
+
`npm pack` a secas no sirve: no se lleva `packages/shared`, que `src/shared.mjs` importa por su ruta (decisión D5). [`scripts/pack.mjs`](scripts/pack.mjs) prepara una copia con `shared/` dentro (y lo mismo con el runner, que lleva además el validador de `ias.json`; y el SDK compilado), la que publica `scripts/publish.mjs` (con `--dry-run`, sin publicar) y la que deja en `.tgz`:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npm run pack:release --prefix packages/cli -- ./release
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
`ias build --local` toma el runner de `packages/runner` en un clon, o el de `node_modules/@imagina-build/ias-runner` junto al CLI (`npm install --global @imagina-build/ias-runner`), o el de `IAS_RUNNER_MODULE`.
|
|
41
41
|
|
|
42
42
|
## 2. Comandos
|
|
43
43
|
|
|
@@ -280,7 +280,7 @@ Con el contrato de [docs/technical/runner-api.md](../../docs/technical/runner-ap
|
|
|
280
280
|
5. Sigue el estado cada 5 s y el registro según llega, hasta que acaba (Ctrl-C deja de seguirla, no la cancela; `--detach` no la sigue). Sale con 1 si no acaba bien.
|
|
281
281
|
6. Con `--wait`, baja sus artefactos a `~/ias-builds/<app>/<plataforma>-<versión>-<número>` (o `--out`) y comprueba el tamaño y el SHA-256 de cada uno.
|
|
282
282
|
|
|
283
|
-
**`ias build --local`** hace lo mismo en este Mac, importando `@imagina-build/ias-runner` (`packages/runner`, o `IAS_RUNNER_MODULE`): una plataforma detrás de otra, a baja prioridad, con el cerrojo de la máquina (`--wait` espera a otra build que lo tenga). El material de firma es `--credentials op://<bóveda>/<elemento>` o una carpeta 700, o `IAS_IOS_CREDENTIALS` e `IAS_ANDROID_CREDENTIALS`. No habla con el servidor.
|
|
283
|
+
**`ias build --local`** hace lo mismo en este Mac, importando `@imagina-build/ias-runner` (`packages/runner`, el instalado junto al CLI, o `IAS_RUNNER_MODULE`): una plataforma detrás de otra, a baja prioridad, con el cerrojo de la máquina (`--wait` espera a otra build que lo tenga). El material de firma es `--credentials op://<bóveda>/<elemento>` o una carpeta 700, o `IAS_IOS_CREDENTIALS` e `IAS_ANDROID_CREDENTIALS`. No habla con el servidor.
|
|
284
284
|
|
|
285
285
|
**`ias builds`**: `list [--status <estado>] [--platform]`, `view <id>`, `logs <id> [--follow]`, `cancel <id>` (quien la lanzó o un `owner`) y `download <id> [--kind ipa,dsyms] [--out <carpeta>]`. El registro es el que mandó el runner, ya limpio de secretos.
|
|
286
286
|
|
|
@@ -391,7 +391,7 @@ No usan red real, claves reales ni 1Password: el certificado lo crea `openssl` e
|
|
|
391
391
|
- `ias init` con la configuración de jscheduler-app y jero-invoices-app;
|
|
392
392
|
- `ias doctor` con programas simulados;
|
|
393
393
|
- `ias sdk install` y el SDK en `ias doctor` (`test/sdk.test.mjs`): el gestor de cada lockfile, de dónde sale el paquete, el plugin en `app.json` sin perder lo que había, lo que falta y cada estado del SDK, con un gestor de paquetes simulado;
|
|
394
|
-
-
|
|
394
|
+
- los `.tgz` del CLI y del runner y la copia que se publica (`test/pack.test.mjs`): llevan `shared/` (el runner, también el validador de `ias.json`), funcionan sin el repositorio al lado, `ias build --local` encuentra el runner instalado junto al CLI, y la copia sale pública y sin `private`;
|
|
395
395
|
- el enrutador, `--json` y los códigos de salida;
|
|
396
396
|
- los envoltorios de `tools/`;
|
|
397
397
|
- **el contrato de la API de gestión** (`test/manage.test.mjs`): un servidor falso responde con las conversaciones de `tests/Fixtures/manage-v1` (escritas a mano o grabadas: el cuerpo vale como valor JSON o como texto), con un `op` y un `security` falsos en el `PATH` (`test/fakes.mjs`), material desechable hecho con `openssl` y `keytool`, y las claves públicas de prueba de `packages/shared/vault/test-vectors/ias-vault-2`. Comprueba lo que pide cada comando, cómo lee cada respuesta y que **ningún secreto** (contraseñas, claves privadas, tokens, el código de dispositivo) sale en stdout, en stderr ni en los argumentos de `op` o `security`;
|
package/package.json
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
// ias build --local: the build on this Mac, by @imagina-build/ias-runner imported here (it is the same pipeline the runners
|
|
2
2
|
// use), one platform after the other, never two at a time. The signing material is the runner's CredentialSource
|
|
3
3
|
// (op://vault/item or a 700 folder); nothing goes to the server. IAS_RUNNER_MODULE points at another copy of the runner.
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
4
5
|
import { resolve } from 'node:path';
|
|
5
6
|
import process from 'node:process';
|
|
6
|
-
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
8
|
import { IasError } from '../../errors.mjs';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Where the runner may be, in order: packages/runner beside packages/cli in this repository, and
|
|
12
|
+
* node_modules/@imagina-build/ias-runner beside node_modules/@imagina-build/ias-cli (npm install --global, or both in a
|
|
13
|
+
* project).
|
|
14
|
+
*/
|
|
15
|
+
export const RUNNER_CANDIDATES = [new URL('../../../../runner/src/index.mjs', import.meta.url), new URL('../../../../ias-runner/src/index.mjs', import.meta.url)];
|
|
10
16
|
|
|
11
|
-
/** The runner module: IAS_RUNNER_MODULE, or the
|
|
17
|
+
/** The URL of the runner module: IAS_RUNNER_MODULE, or the first of `candidates` that exists (the first if none does). */
|
|
18
|
+
export function runnerLocation({ env, cwd }, candidates = RUNNER_CANDIDATES) {
|
|
19
|
+
if (env.IAS_RUNNER_MODULE) return pathToFileURL(resolve(cwd, env.IAS_RUNNER_MODULE));
|
|
20
|
+
return candidates.find((candidate) => existsSync(fileURLToPath(candidate))) ?? candidates[0];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The runner module, from runnerLocation. */
|
|
12
24
|
async function runnerModule(context) {
|
|
13
|
-
const where =
|
|
25
|
+
const where = runnerLocation(context);
|
|
14
26
|
try {
|
|
15
27
|
return await import(where.href);
|
|
16
28
|
} catch (error) {
|
|
17
|
-
throw new IasError(`@imagina-build/ias-runner is not at ${where.pathname} (${error.code ?? error.message})`, { fix: 'ias build --local needs the runner
|
|
29
|
+
throw new IasError(`@imagina-build/ias-runner is not at ${where.pathname} (${error.code ?? error.message})`, { fix: 'ias build --local needs the runner: npm install --global @imagina-build/ias-runner (beside ias), packages/runner beside the CLI in the repository, or IAS_RUNNER_MODULE pointing at its src/index.mjs', code: 'runner_missing' });
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
|
|
@@ -13,15 +13,18 @@ const PROFILE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,49}$/;
|
|
|
13
13
|
|
|
14
14
|
/** Why a field of eas.json does not go to ias.json. */
|
|
15
15
|
const LEFT_OUT = {
|
|
16
|
-
env: 'variables go to ias env
|
|
17
|
-
credentialsSource: 'credentials go to the vault
|
|
18
|
-
ascApiKeyPath: 'credentials go to the vault
|
|
19
|
-
ascApiKeyId: 'credentials go to the vault
|
|
20
|
-
ascApiKeyIssuerId: 'credentials go to the vault
|
|
21
|
-
serviceAccountKeyPath: 'credentials go to the vault
|
|
22
|
-
appleId: 'credentials go to the vault
|
|
16
|
+
env: 'variables go to ias env, sealed when they are secret: ias migrate eas env brings those of EAS',
|
|
17
|
+
credentialsSource: 'credentials go to the vault: ias credentials import, or ias migrate eas',
|
|
18
|
+
ascApiKeyPath: 'credentials go to the vault: ias credentials import, or ias migrate eas',
|
|
19
|
+
ascApiKeyId: 'credentials go to the vault: ias credentials import, or ias migrate eas',
|
|
20
|
+
ascApiKeyIssuerId: 'credentials go to the vault: ias credentials import, or ias migrate eas',
|
|
21
|
+
serviceAccountKeyPath: 'credentials go to the vault: ias credentials import, or ias migrate eas',
|
|
22
|
+
appleId: 'credentials go to the vault: ias credentials import, or ias migrate eas',
|
|
23
23
|
};
|
|
24
|
-
const leftOutReason = (key) => LEFT_OUT[key] ?? 'ias.json has no field for it
|
|
24
|
+
const leftOutReason = (key) => LEFT_OUT[key] ?? 'ias.json has no field for it (schema/ias.schema.json lists what its profiles take)';
|
|
25
|
+
|
|
26
|
+
/** Why eas.json › cli stays out, and where the build numbers go: IAS keeps them on its server (ias version). */
|
|
27
|
+
const cliReason = (cli) => `it configures eas-cli; IAS keeps the build numbers on its server, and autoIncrement takes the next: ${cli?.appVersionSource === 'remote' ? 'EAS keeps them now (appVersionSource remote), and ias migrate eas version brings them' : 'ias version set --from-app-json brings those of app.json'}`;
|
|
25
28
|
|
|
26
29
|
/** The code of an application made from a slug: lowercase letters, digits and hyphens, up to 32. */
|
|
27
30
|
const codeFromSlug = (slug) => (typeof slug === 'string' ? slug.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 32) : undefined);
|
|
@@ -114,7 +117,7 @@ export function initialConfig(dir, { app: appOption } = {}) {
|
|
|
114
117
|
config.submit = submit;
|
|
115
118
|
from.submit = 'eas.json';
|
|
116
119
|
}
|
|
117
|
-
for (const key of Object.keys(eas ?? {}).filter((candidate) => !['build', 'submit'].includes(candidate))) leave(`eas.json › ${key}`, key === 'cli' ?
|
|
120
|
+
for (const key of Object.keys(eas ?? {}).filter((candidate) => !['build', 'submit'].includes(candidate))) leave(`eas.json › ${key}`, key === 'cli' ? cliReason(eas.cli) : leftOutReason(key));
|
|
118
121
|
|
|
119
122
|
const secrets = writtenSecrets(config);
|
|
120
123
|
const errors = validate(config);
|
package/src/team-vault.mjs
CHANGED
|
@@ -32,7 +32,15 @@ export function vaultItem(context, project, server) {
|
|
|
32
32
|
return { reference, vault: match[1], title: match[2] };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Runs op. With `input`, through cat: Node gives a child a socket for stdin, not a pipe, and op only reads a template
|
|
37
|
+
* from a pipe ("provide the item category with '--category' flag" otherwise). The template never touches the disk nor
|
|
38
|
+
* the arguments.
|
|
39
|
+
*/
|
|
40
|
+
const op = (args, { env, input } = {}) => {
|
|
41
|
+
const [command, commandArgs] = input === undefined ? ['op', args] : ['/bin/sh', ['-c', 'cat | op "$@"', 'sh', ...args]];
|
|
42
|
+
return spawnSync(command, commandArgs, { encoding: 'utf8', env, input, maxBuffer: 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
43
|
+
};
|
|
36
44
|
|
|
37
45
|
/** The first line of what op said, which names the reference and never a value. */
|
|
38
46
|
const opReason = (result) => (result.error ? result.error.message : (result.stderr || '').trim().split('\n').at(-1) || `exit ${result.status}`);
|