I've used two techniques, the first works for all field types except memo fields. Basically it uses the SQL Select capability to create new fields. Some sample code follows:
lcdbf = "customer"
SELECT 0
USE (lcdbf)
IF ! chkfldx("fax"

SELECT *, SPACE(13) AS fax, SPACE(3) AS country, ;
SPACE(7) AS user_id, {} AS LastUpdate, ;
SPACE(25) as email, 0000.00 as CustCred, ;
.F. as NewUser ;
FROM (lcdbf) ;
INTO TABLE xtemp.dbf
USE IN xtemp
USE IN (lcdbf)
ERASE (lcdbf+".DBF"

RENAME xtemp.dbf TO (lcdbf+".DBF"

ELSE
USE IN (m.lcdbf)
ENDIF
.....
FUNCTION chkfldx && Check for field already eXisting in DBF
PARAMETERS zcfield
PRIVATE lnii
zcfield = UPPER(ALLTRIM(zcfield))
FOR lnii = 1 to FCOUNT()
IF ALLTRIM(FIELD(lnii)) == zcfield
RETURN .T.
ENDIF
ENDFOR
RETURN .F.
Adding fields to Customer.DBF
fax C(13)
country C(3)
user_id C(7)
LastUpdate D
email C(25)
CustCred N(7,2)
NewUser L
Another way is to use AFIELDS() to get the field information, re-DIMENSION the array for the number of additional fields. Then fill in the additional field information, and use this with CREATE TABLE ... FROM ARRAY to create an empty table with the new structure. Finally use APPEND to transfer the data from the "old" table to the new one.
A variant of this technique is to use COPY STRUCTURE EXTENDED to create a table with the field information, add new records for the new fields and use CREATE <newtable> FROM <structTable> to make the new table. Finish again by transfering the data.
All techniques will likely require you to recreate the indexes (.CDXs) - especially if the new fields are indexed.
Rick